# SDPEnv.py # Utilities for creating or validating an environment based on a master configuration file #============================================================================== # Copyright and license info is available in the LICENSE file included with # the Server Deployment Package (SDP), and also available online: # https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE #------------------------------------------------------------------------------ from __future__ import print_function import os import os.path import sys import subprocess import socket import shutil import glob import re import textwrap import logging import argparse import hashlib import stat import datetime from collections import defaultdict # Python 2.7/3.3 compatibility. python3 = sys.version_info[0] >= 3 if python3: from configparser import ConfigParser from io import StringIO else: from ConfigParser import ConfigParser from StringIO import StringIO MODULE_NAME = 'SDPEnv' DEFAULT_CFG_FILE = 'sdp_master_config.ini' DEFAULT_LOG_FILE = '%s.log' % MODULE_NAME DEFAULT_VERBOSITY = 'INFO' DEFAULT_SDP_GLOBAL_ROOT = r'c:' LOGGER_NAME = '%s.log' % MODULE_NAME # Default values when configuring a new server TEMPLATE_SERVER_CONFIGURABLES = "template_configure_new_server.bat" valid_replica_types = ["replica", "forwarding-replica", "build-server", "edge-server", "standby", "forwarding-standby"] valid_service_types = valid_replica_types[:] valid_service_types.extend(["standard", "commit-server"]) class SDPException(Exception): "Base exceptions" pass class SDPConfigException(SDPException): "Exceptions in config" pass def readTemplateServerConfigurables(): """Returns the file as a list, with only the interesting lines""" path = os.path.join(os.path.dirname(__file__), TEMPLATE_SERVER_CONFIGURABLES) with open(path) as fh: lines = [line.strip() for line in fh.readlines()] result = [line for line in lines if not line.startswith("::")] return result def copy_file(sourcefile, dest): "Handle if target is read-only" destfile = dest if os.path.isdir(dest): destfile = os.path.join(dest, os.path.basename(sourcefile)) if os.path.exists(destfile): os.chmod(destfile, stat.S_IWRITE) shutil.copy(sourcefile, destfile) def remove_config_whitespace(config_filename): separator = "=" with open(config_filename, "r") as fh: lines = fh.readlines() fp = open(config_filename, "w") for line in lines: line = line.strip() if not line.startswith("#") and separator in line: assignment = line.split(separator, 1) assignment = [x.strip() for x in assignment] fp.write("%s%s%s\n" % (assignment[0], separator, assignment[1])) else: fp.write(line + "\n") def merge_configs(src_filename, dest, config_data=None): """Merge the specified values in the config files""" dest_filename = dest if os.path.isdir(dest): dest_filename = os.path.join(dest, os.path.basename(src_filename)) src_config_file = open(src_filename) dest_config_file = open(dest_filename) src_config = ConfigParser() dest_config = ConfigParser() if python3: src_config.read_file(src_config_file) dest_config.read_file(dest_config_file) else: src_config.readfp(src_config_file) dest_config.readfp(dest_config_file) for src_section in src_config.sections(): if not dest_config.has_section(src_section): dest_config.add_section(src_section) for name, value in src_config.items(src_section): dest_config.set(src_section, name, value) dest_config_file.close() with open(dest_filename, "w") as dest_config_file: dest_config.write(dest_config_file) remove_config_whitespace(dest_filename) def file_md5(filename): m = hashlib.md5() with open(filename, mode = 'rb') as fh: contents = fh.read() m.update(contents) return m.digest() def contents_md5(contents): m = hashlib.md5() if python3: m.update(contents.encode()) else: m.update(contents) return m.digest() def files_different(src, dest, src_contents=None): "Decide if source and dest are different - note dest might be a dir" if not os.path.exists(dest): return True destfile = dest if os.path.isdir(dest): destfile = os.path.join(dest, os.path.basename(src)) if not os.path.exists(destfile): return True if src_contents: md5src = contents_md5(src_contents.replace("\n", "\r\n")) else: md5src = file_md5(src) md5dest = file_md5(destfile) return md5src != md5dest def joinpath(root, *path_elts): "Deals with drive roots or a full path as the root" if len(root) > 0 and root[-1] == ":": return os.path.join(root, os.sep, *path_elts) else: return os.path.join(root, *path_elts) def running_as_administrator(): "Makes sure current process has admin rights" output = "" try: output = subprocess.check_output("net session", universal_newlines=True, stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: output = e.output return not re.search(r"Access is denied", output) def mklink(linkname, target): "Create the specified link" output = "" try: output = subprocess.check_output('mklink /d "%s" "%s"' % (linkname, target), universal_newlines=True, stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: output = e.output except UnicodeDecodeError as e: raise SDPException("Unicode error calling subprocess - if using Python 3 then consider setting code page: chcp 850") if not os.path.exists(linkname): raise SDPException("Error creating link '%s' to '%s':\n%s" % ( linkname, target, output)) def find_record(rec_list, key_field, search_key): "Returns dictionary found in list of them" for rec in rec_list: if rec[key_field].lower() == search_key.lower(): return rec return None def rtrim_slash(dir_path): "Remove trailing slash if it exists" if dir_path[-1] == os.sep: dir_path = dir_path[:-1] return dir_path class SDPInstance(object): "A single instance" def __init__(self, config, section, options): "Expects a configparser" self._attrs = {} self.options = options def_dir_attrs = ('sdp_global_root metadata_root depotdata_root logdata_root').split() def_attrs = ('sdp_serverid sdp_service_type sdp_p4port_number ' 'sdp_p4superuser_password email_password remote_depotdata_root').split() def_attrs.extend(def_dir_attrs) for def_attr in def_attrs: self._attrs[def_attr] = "" if not ":" in section: raise SDPConfigException("Section names must be of format [<SDP_INSTANCE>:<SDP_HOSTNAME>]") sdp_instance, sdp_hostname = section.split(":") default_hostname = "__OUTPUT_OF_HOSTNAME_COMMAND_ON_SERVER_PLEASE_UPDATE__" current_hostname = socket.gethostname() if sdp_hostname.upper() == default_hostname: msg = "Section name '%s' still contains default value\n" % section msg += "Please change hostname (after colon) to real value,\n" msg += "e.g. current machine hostname:\n '%s'" % current_hostname raise SDPConfigException(msg) self._attrs['sdp_instance'] = sdp_instance self._attrs['sdp_hostname'] = sdp_hostname for item in config.items(section): if item[0] in def_dir_attrs: self._attrs[item[0]] = rtrim_slash(item[1]) else: self._attrs[item[0]] = item[1] self._init_dirs() def __iter__(self): return self._attrs.__iter__() def _init_dirs(self): """Initialises directory names for this instance""" curr_scriptdir = os.path.dirname(os.path.realpath(__file__)) self._attrs["sdp_global_root_dir"] = DEFAULT_SDP_GLOBAL_ROOT if 'sdp_global_root' in self._attrs and len(self.sdp_global_root) > 0: self._attrs["sdp_global_root_dir"] = self.sdp_global_root self._attrs["installer_sdp_common_bin_dir"] = os.path.abspath(os.path.join(curr_scriptdir, '..', 'p4', 'common', 'bin')) # These dirs contain links as part of them self._attrs["instance_dir"] = joinpath(self.sdp_global_root_dir, 'p4', self.sdp_instance) self._attrs["bin_dir"] = joinpath(self.instance_dir, 'bin') self._attrs["common_dir"] = joinpath(self.sdp_global_root_dir, 'p4', 'common') self._attrs["common_bin_dir"] = joinpath(self.common_dir, 'bin') self._attrs["root_dir"] = joinpath(self.instance_dir, 'root') self._attrs["checkpoints_dir"] = joinpath(self.instance_dir, 'checkpoints') self._attrs["depots_dir"] = joinpath(self.instance_dir, 'depots') self._attrs["logs_dir"] = joinpath(self.instance_dir, 'logs') self._attrs["sdp_config_dir"] = joinpath(self.sdp_global_root_dir, 'p4', 'config') def links_and_dirs(self): """return things in order - real directories before the links to them""" metadata_instance = joinpath(self.metadata_root, 'p4', self.sdp_instance) depotdata_instance = joinpath(self.depotdata_root, 'p4', self.sdp_instance) logdata_instance = joinpath(self.logdata_root, 'p4', self.sdp_instance) return([ (None, joinpath(self.sdp_global_root_dir, 'p4')), (None, metadata_instance), (self.instance_dir, depotdata_instance), (None, logdata_instance), (self.common_dir, joinpath(self.depotdata_root, 'p4', 'common')), (self.sdp_config_dir, joinpath(self.depotdata_root, 'p4', 'config')), (None, self.common_bin_dir), (None, joinpath(self.common_bin_dir, 'triggers')), (None, self.bin_dir), (None, joinpath(self.instance_dir, 'tmp')), (None, joinpath(self.instance_dir, 'depots')), (None, joinpath(self.instance_dir, 'checkpoints')), (None, joinpath(self.instance_dir, 'ssl')), (self.root_dir, joinpath(metadata_instance, 'root')), (None, joinpath(self.root_dir, 'save')), (joinpath(self.instance_dir, 'offline_db'), joinpath(metadata_instance, 'offline_db')), (self.logs_dir, joinpath(logdata_instance, 'logs')) ]) def __getitem__(self, name): return object.__getattribute__(self, '_attrs').get(name) def is_current_host(self): """Checks against current hostname""" return socket.gethostname().lower() == self._attrs["sdp_hostname"].lower() def is_specified_instance(self): """Checks against global options""" if not self.options.specified_instance: return True return self.options.specified_instance.lower() == self._attrs["sdp_instance"].lower() def __getattribute__(self, name): """Only allow appropriate attributes""" if name in ["_attrs", "_init_dirs", "get", "is_current_host", "is_specified_instance", "options", "links_and_dirs"]: return object.__getattribute__(self, name) else: if not name in object.__getattribute__(self, '_attrs'): raise AttributeError("Unknown attribute '%s'" % name) return object.__getattribute__(self, '_attrs').get(name, "") class SDPConfig(object): """The main class to process SDP configurations""" def __init__(self, config_data=None, logStream=None): self.config = None if logStream is None: logStream = sys.stdout self.instances = {} self.commands = [] # List of command files to run - and their order self.options = None self.parse_args() self.logger = logging.getLogger(LOGGER_NAME) self.logger.setLevel(logging.INFO) h = logging.StreamHandler(logStream) bf = logging.Formatter('%(levelname)s: %(message)s') h.setFormatter(bf) self.logger.addHandler(h) self.logger.debug("Command Line Options: %s\n" % self.options) self._read_config(self.options.config_filename, config_data) def parse_args(self): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\ NAME SDPEnv.py VERSION 1.0.0 DESCRIPTION Create the environment for an SDP (Server Deployment Package) EXAMPLES SDPEnv.py --help '''), epilog="Copyright (c) 2008-2014 Perforce Software, Inc. " "See LICENSE file for legal information and disclaimers." ) parser.add_argument('-y', '--yes', action='store_true', help="Perform actual changes such as directory creation and file copying." "Without this flag the tool is effectively in reporting mode only.") parser.add_argument('-c', '--config_filename', default=DEFAULT_CFG_FILE, help="Master config file, relative or absolute path. Default: " + DEFAULT_CFG_FILE) parser.add_argument('-i', '--instance', dest='specified_instance', help="Configure specified instance only (ignoring others specified in master config file). " "Useful for adding a new instance to an existing configuration.") self.options = parser.parse_args() def _read_config(self, config_filename, config_data): """Read the configuration file""" if config_data: # testing config_file = StringIO(config_data) else: if not os.path.exists(config_filename): raise SDPException("Master config file not found: '%s'" % config_filename) config_file = open(config_filename) self.config = ConfigParser() if python3: self.config.read_file(config_file) else: self.config.readfp(config_file) self.logger.info("Found the following sections: %s" % self.config.sections()) for section in self.config.sections(): self.instances[section] = SDPInstance(self.config, section, self.options) def isvalid_config(self): """Check configuration read is valid""" required_options = ("sdp_serverid sdp_service_type sdp_hostname sdp_instance sdp_p4port_number " "metadata_root depotdata_root logdata_root").split() errors = [] specified_instance_found = False for instance_name in self.instances.keys(): missing = [] fields = {} instance = self.instances[instance_name] if instance.is_specified_instance(): specified_instance_found = True # Check for require fields for opt in required_options: if instance[opt] == "": missing.append(opt) else: fields[opt] = instance[opt] if missing: errors.append("The following required options are missing '%s' in instance '%s'" % ( ", ".join(missing), instance_name)) # Check for numeric fields field_name = "sdp_p4port_number" if field_name in fields: if not instance[field_name].isdigit(): errors.append("%s must be numeric in instance '%s'" % (field_name.upper(), instance_name)) # Check for restricted values field_name = "sdp_service_type" if field_name in fields: if not instance[field_name] in valid_service_types: errors.append("%s must be one of '%s' in instance '%s'" % (field_name.upper(), ", ".join(valid_service_types), instance_name)) # Replicas should specify a couple of fields if instance.sdp_service_type in valid_replica_types: for field_name in ["remote_depotdata_root"]: if instance[field_name] == "": errors.append("Field %s must have a value for replica instance '%s'" % (field_name.upper(), instance_name)) if self.options.specified_instance and not specified_instance_found: errors.append("Instance '%s' specified but not found in the config file" % self.options.specified_instance) if errors: raise SDPConfigException("\n".join(errors)) return True def get_master_instance_name(self): """Assumes valid config""" #TODO - this assumes only one standard section for instance_name in self.instances: if self.instances[instance_name]["sdp_service_type"] in ["standard", "commit-server"]: return instance_name raise SDPConfigException("No master section found") def write_master_config_ini(self): """Write the appropriate configure values""" common_settings = """sdp_serverid sdp_p4serviceuser sdp_global_root sdp_p4superuser admin_pass_filename email_pass_filename mailfrom maillist mailhost mailhostport python remote_depotdata_root keepckps keeplogs limit_one_daily_checkpoint""".split() instance_names = self.instances.keys() master_name = self.get_master_instance_name() master_instance = self.instances[master_name] lines = [] lines.append("# Global sdp_config.ini") lines.append("") for instance_name in instance_names: instance = self.instances[instance_name] if not instance.is_specified_instance(): continue lines.append("\n[%s:%s]" % (instance.sdp_instance, instance.sdp_hostname)) lines.append("%s=%s:%s" % ("p4port", instance.sdp_hostname, instance.sdp_p4port_number)) for setting in common_settings: lines.append("%s=%s" % (setting, instance[setting])) if instance.sdp_service_type in valid_replica_types: lines.append("remote_sdp_instance=%s" % (master_instance.sdp_instance)) lines.append("p4target=%s:%s" % (master_instance.sdp_hostname, master_instance.sdp_p4port_number)) else: lines.append("remote_sdp_instance=") lines.append("p4target=") sdp_config_file = "sdp_config.ini" self.commands.append(sdp_config_file) self.logger.info("Config file written: %s" % sdp_config_file) with open(sdp_config_file, "w") as fh: for line in lines: fh.write("%s\n" % line) def get_configure_bat_contents(self, templateLines=None): """Return the information to be written into configure bat files per instance""" if templateLines is None: templateLines = readTemplateServerConfigurables() cmd_lines = {} # indexed by instance name instance_names = self.instances.keys() master_instance_name = self.get_master_instance_name() master_instance = self.instances[master_instance_name] master_id = master_instance.sdp_serverid cmd_lines[master_id] = [] p4cmd = "p4 -p %s:%s -u %s" % (master_instance.sdp_hostname, master_instance.sdp_p4port_number, master_instance.sdp_p4superuser) p4configurecmd = "%s configure set" % (p4cmd) if master_instance.is_specified_instance(): path = joinpath(master_instance.checkpoints_dir, "p4_%s" % master_instance.sdp_instance) cmd_lines[master_id].append("%s %s#journalPrefix=%s" % (p4configurecmd, master_instance.sdp_serverid, path)) cmd_lines[master_id].append("%s %s#server.depot.root=%s" % (p4configurecmd, master_instance.sdp_serverid, master_instance.depots_dir)) for line in [x.strip() for x in templateLines]: if line: newLine = line.replace("p4 configure set", p4configurecmd) if newLine.startswith("p4 counter SDP"): newLine = '%s counter SDP "%s"' % (p4cmd, datetime.date.today().isoformat()) cmd_lines[master_id].append(newLine) # Now set up all the config variables for replication for instance_name in [s for s in instance_names if s != master_id]: instance = self.instances[instance_name] if not instance.is_specified_instance(): continue if not instance.sdp_service_type in ["replica", "forwarding-replica", "build-server", "edge-server", "standby", "forwarding-standby"]: continue path = joinpath(instance.checkpoints_dir, "p4_%s" % instance.sdp_instance) cmd_lines[master_id].append("%s %s#journalPrefix=%s" % (p4configurecmd, instance.sdp_serverid, path)) cmd_lines[master_id].append("%s %s#server.depot.root=%s" % (p4configurecmd, instance.sdp_serverid, instance.depots_dir)) cmd_lines[master_id].append('%s %s#P4TARGET=%s:%s' % (p4configurecmd, instance.sdp_serverid, master_instance.sdp_hostname, master_instance.sdp_p4port_number)) tickets_path = joinpath(instance.instance_dir, "p4tickets.txt") cmd_lines[master_id].append('%s %s#P4TICKETS=%s' % (p4configurecmd, instance.sdp_serverid, tickets_path)) log_path = joinpath(instance.logs_dir, "%s.log" % instance.sdp_serverid) cmd_lines[master_id].append('%s %s#P4LOG=%s' % (p4configurecmd, instance.sdp_serverid, log_path)) start_ind = 2 if instance.sdp_service_type in ["standby", "forwarding-standby"]: cmd_lines[master_id].append('%s %s#rpl.journalcopy.location=1' % (p4configurecmd, instance.sdp_serverid)) cmd_lines[master_id].append('%s "%s#startup.1=journalcopy -i 1"' % (p4configurecmd, instance.sdp_serverid)) cmd_lines[master_id].append('%s "%s#startup.2=pull -L -i 1"' % (p4configurecmd, instance.sdp_serverid)) start_ind = 3 else: cmd_lines[master_id].append('%s "%s#startup.1=pull -i 1"' % (p4configurecmd, instance.sdp_serverid)) for i in range(start_ind, start_ind + 4): cmd_lines[master_id].append('%s "%s#startup.%d=pull -u -i 1"' % (p4configurecmd, instance.sdp_serverid, i)) cmd_lines[master_id].append('%s %s#lbr.replication=readonly' % (p4configurecmd, instance.sdp_serverid)) cmd_lines[master_id].append('%s %s#db.replication=readonly' % (p4configurecmd, instance.sdp_serverid)) if instance.sdp_service_type in ["forwarding-replica"]: cmd_lines[master_id].append('%s %s#rpl.forward.all=1' % (p4configurecmd, instance.sdp_serverid)) cmd_lines[master_id].append('%s %s#serviceUser=%s' % (p4configurecmd, instance.sdp_serverid, instance.sdp_serverid)) return cmd_lines def write_configure_bat_contents(self, cmd_lines): "Write the appropriate configure bat files for respective instances" command_files = [] for instance_name in cmd_lines.keys(): command_file = "configure_%s.bat" % (instance_name) command_files.append(command_file) with open(command_file, "w") as fh: for line in cmd_lines[instance_name]: fh.write("%s\n" % line) return command_files def get_instance_links_and_dirs(self): """ Get a list of instance dirs valid on the current machine. Returned as tuples of (link_name, link_target). """ links_and_dirs = [] instance_names = sorted(self.instances.keys()) for instance_name in instance_names: instance = self.instances[instance_name] # Only create dirs when we are on the correct hostname if not instance.is_specified_instance(): continue if not instance.is_current_host(): self.logger.info("Ignoring directories on '%s' for instance '%s'" % (instance.sdp_hostname, instance.sdp_instance)) continue links_and_dirs.extend(instance.links_and_dirs()) # Remove duplicates result = [] for dl in links_and_dirs: if dl not in result: result.append(dl) return result def check_src_files_exist(self, files_to_copy_list): missing_src_files = [] for src, dest in files_to_copy_list: if not os.path.exists(src) and not src in missing_src_files: missing_src_files.append(src) return missing_src_files def mk_links_and_dirs(self, links_and_dirs, files_to_copy_list, files_to_merge_list): "Make all appropriate directories on this machine and copy in files" if not self.options.yes: self.logger.info("The following directories/links would be created with the -y/--yes flag") missing_src_files = self.check_src_files_exist(files_to_copy_list) if missing_src_files: raise SDPException("Missing files to copy to instance: '%s'" % (", ".join(missing_src_files))) for linkname, target in links_and_dirs: if linkname: if not os.path.exists(target): self.logger.info("Creating target dir '%s'" % target) if self.options.yes: os.makedirs(target) if not os.path.exists(linkname): self.logger.info("Creating link '%s' to '%s'" % (linkname, target)) if self.options.yes: mklink(linkname, target) else: if not os.path.exists(target): self.logger.info("Creating target dir '%s'" % target) if self.options.yes: os.makedirs(target) files_copied = defaultdict(list) for file_pair in files_to_copy_list: src, dest = file_pair if files_different(src, dest): if src not in files_copied or dest not in files_copied[src]: files_copied[src].append(dest) self.logger.info("Copying '%s' to '%s'" % (src, dest)) if self.options.yes: copy_file(src, dest) for file_pair in files_to_merge_list: src, dest = file_pair self.logger.info("Merging '%s' into '%s'" % (src, dest)) if self.options.yes: merge_configs(src, dest) instance_names = self.instances.keys() for instance_name in instance_names: instance = self.instances[instance_name] if not instance.is_specified_instance(): continue # Only create dirs when we are on the correct hostname if not instance.is_current_host(): self.logger.info("Ignoring directories on '%s' for instance '%s'" % (instance.sdp_hostname, instance.sdp_instance)) continue for filename in ['daily-backup.bat', 'p4verify.bat', 'replica-status.bat']: dest_filename = os.path.join(instance.bin_dir, filename) src_contents = self.instance_bat_contents(filename, instance.sdp_instance, instance.common_bin_dir) if files_different(None, dest_filename, src_contents): self.logger.info("Creating instance bat file '%s'" % (dest_filename)) if self.options.yes: self.create_instance_bat(dest_filename, src_contents) if self.options.yes: admin_pass_filename = os.path.join(instance.sdp_config_dir, instance.admin_pass_filename) with open(admin_pass_filename, "w") as fh: fh.write(instance.sdp_p4superuser_password) email_pass_filename = os.path.join(instance.sdp_config_dir, instance.email_pass_filename) with open(email_pass_filename, "w") as fh: fh.write(instance.email_password) def get_instance_files_to_copy(self): "Get a list of all files to copy to the instances" instance_names = self.instances.keys() curr_scriptdir = os.path.dirname(os.path.realpath(__file__)) file_list = [] for instance_name in instance_names: instance = self.instances[instance_name] if not instance.is_specified_instance(): continue # Only create dirs when we are on the correct hostname if not instance.is_current_host(): self.logger.info("Ignoring files to copy on '%s' for instance '%s'" % (instance.sdp_hostname, instance.sdp_instance)) continue for filename in glob.glob(os.path.join(instance.installer_sdp_common_bin_dir, '*.*')): file_list.append((filename, os.path.join(instance.common_bin_dir, os.path.basename(filename)))) for filename in glob.glob(os.path.join(instance.installer_sdp_common_bin_dir, 'triggers', '*.*')): file_list.append((filename, os.path.join(instance.common_bin_dir, 'triggers', os.path.basename(filename)))) file_list.append((os.path.join(curr_scriptdir, 'p4.exe'), instance.bin_dir)) file_list.append((os.path.join(curr_scriptdir, 'p4d.exe'), instance.bin_dir)) file_list.append((os.path.join(curr_scriptdir, 'p4d.exe'), os.path.join(instance.bin_dir, 'p4s.exe'))) if not self.options.specified_instance: file_list.append((os.path.join(curr_scriptdir, 'sdp_config.ini'), instance.sdp_config_dir)) serverid_file = os.path.join(curr_scriptdir, '%s_server.id' % instance.sdp_serverid) with open(serverid_file, "w") as fh: fh.write("%s" % instance.sdp_serverid) file_list.append((serverid_file, os.path.join(instance.root_dir, 'server.id'))) return file_list def get_files_to_merge(self): "Get a list of all files to update - only if an instance is specified" file_list = [] if not self.options.specified_instance: return file_list instance_names = self.instances.keys() curr_scriptdir = os.path.dirname(os.path.realpath(__file__)) for instance_name in instance_names: instance = self.instances[instance_name] if not instance.is_specified_instance(): continue file_list.append((os.path.join(curr_scriptdir, 'sdp_config.ini'), instance.sdp_config_dir)) return file_list def bat_file_hostname_guard_lines(self, hostname): lines = ['@echo off', 'FOR /F "usebackq" %%i IN (`hostname`) DO SET HOSTNAME=%%i', 'if /i "%s" NEQ "%s" (' % ('%HOSTNAME%', hostname), ' echo ERROR: This command file should only be run on machine with hostname "%s"' % (hostname), ' exit /b 1', ')', '@echo on'] return lines def get_service_install_cmds(self): "Configure any services on the current machine" cmds = {} instance_names = sorted(self.instances.keys()) for instance_name in instance_names: instance = self.instances[instance_name] hostname = instance.sdp_hostname.lower() if not instance.is_specified_instance(): continue self.logger.info("Creating service configure commands on '%s' for instance '%s' in install_services_%s.bat" % ( hostname, instance.sdp_instance, hostname)) # Install services if hostname not in cmds: cmds[hostname] = [] instsrv = joinpath(instance.common_bin_dir, 'svcinst.exe') cmd = '%s create -n p4_%s -e "%s" -a' % (instsrv, instance.sdp_instance, os.path.join(instance.bin_dir, 'p4s.exe')) cmds[hostname].append(cmd) p4cmd = os.path.join(instance.bin_dir, 'p4.exe') cmds[hostname].append('%s set -S p4_%s P4ROOT=%s' % (p4cmd, instance.sdp_instance, instance.root_dir)) cmds[hostname].append('%s set -S p4_%s P4JOURNAL=%s' % (p4cmd, instance.sdp_instance, os.path.join(instance.logs_dir, 'journal'))) cmds[hostname].append('%s set -S p4_%s P4NAME=%s' % (p4cmd, instance.sdp_instance, instance.sdp_serverid)) cmds[hostname].append('%s set -S p4_%s P4PORT=%s' % (p4cmd, instance.sdp_instance, instance.sdp_p4port_number)) log_path = joinpath(instance.logs_dir, "%s.log" % instance.sdp_serverid) cmds[hostname].append('%s set -S p4_%s P4LOG=%s' % (p4cmd, instance.sdp_instance, log_path)) return cmds def write_service_install_cmds(self, cmds): "Configure any services on the various machines" command_files = [] if not cmds: return command_files for instance_name in self.instances: instance = self.instances[instance_name] hostname = instance.sdp_hostname.lower() if hostname in cmds: command_file = "install_services_%s.bat" % hostname if not command_file in command_files: command_files.append(command_file) with open(command_file, "w") as fh: # Write a safeguarding header for specific hostname lines = self.bat_file_hostname_guard_lines(hostname) lines.extend(cmds[hostname]) for line in lines: fh.write("%s\n" % line) return command_files def instance_bat_contents(self, fname, instance, common_bin_dir): "Creates instance specific batch files which call common one" hdrlines = """::----------------------------------------------------------------------------- :: Copyright (c) 2012-2017 Perforce Software, Inc. Provided for use as defined in :: the Perforce Consulting Services Agreement. ::----------------------------------------------------------------------------- set ORIG_DIR=%CD% """.split("\n") lines = [line.strip() for line in hdrlines] lines.append("") lines.append('cd /d "%s"\n' % common_bin_dir) lines.append('powershell -file %s %s\n' % (fname.replace(".bat", ".ps1"), instance)) lines.append('cd /d %ORIG_DIR%\n') return "\n".join(lines) def create_instance_bat(self, dest_filename, contents): "Creates instance specific batch files which call common one" with open(dest_filename, "w") as fh: fh.write(contents) def process_config(self): "Process and produce the various files" self.isvalid_config() if self.options.yes and not running_as_administrator(): raise SDPException("This action must be run with Administrator rights") self.write_master_config_ini() links_and_dirs = self.get_instance_links_and_dirs() files_to_copy = self.get_instance_files_to_copy() files_to_merge = self.get_files_to_merge() self.mk_links_and_dirs(links_and_dirs, files_to_copy, files_to_merge) cmds = self.get_service_install_cmds() command_files = self.write_service_install_cmds(cmds) cmd_lines = self.get_configure_bat_contents() command_files.extend(self.write_configure_bat_contents(cmd_lines)) print("\n\n") if self.options.yes: print("Please run the following commands:") else: print("The following commands have been created - but you are in report mode so no directories have been created") for cmd in command_files: print(" %s" % cmd) print("You will also need to seed the replicas from a checkpoint and run the appropriate commands on those machines") if not self.options.yes: self.logger.info("Running in reporting mode: use -y or --yes to perform actions.") def main(): "Initialization. Process command line argument and initialize logging." try: sdpconfig = SDPConfig() sdpconfig.process_config() except SDPException as e: print(str(e)) except SDPConfigException as e: print(str(e)) if __name__ == '__main__': main()
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#13 | 27973 | Robert Cowham | Fix Python2 incompatibility with .copy() not implemented. | ||
#12 | 26665 | Robert Cowham |
Make sure we use svcinst.exe not instsrv.exe Add notes for SDP-functions.ps1 Add section on email troubleshooting and mention use of stunnel if required (where implicit SSL is used by server). |
||
#11 | 26662 | Robert Cowham | Output blank lines as placeholders in sdp_config.ini | ||
#10 | 26527 | Robert Cowham |
Update with new location of adminpass. Server types can be standard or commit-server. |
||
#9 | 26109 | Robert Cowham | Handle new replica types: standby, forwarding-standby, edge-server, commit-server | ||
#8 | 21604 | Robert Cowham | Reinstate the instance specific .bat files - which call the relevant .ps1 versions | ||
#7 | 21142 | Robert Cowham | Merge from main | ||
#6 | 20541 | Robert Cowham | Add new values for sending email: mailhostport and email_pass_filename | ||
#5 | 16029 | C. Thomas Tyler |
Routine merge to dev from main using: p4 merge -b perforce_software-sdp-dev |
||
#4 | 15701 | C. Thomas Tyler | Routine merge down using 'p4 merge -b perforce_software-sdp-dev'. | ||
#3 | 12028 | C. Thomas Tyler | Refreshed SDP dev branch, merging down from main. | ||
#2 | 10996 | Robert Cowham |
Remove P4 requirement for basic SDPEnv.py - moved to report_env.py instead. Remove (oudataed) copies from OS Setup for now - duplicates cause a problem |
||
#1 | 10961 | C. Thomas Tyler | Merge down from main. | ||
//guest/perforce_software/sdp/main/Server/Windows/setup/SDPEnv.py | |||||
#1 | 10872 | C. Thomas Tyler |
Added Windows SDP into The Workshop: * Combined (back) into Unix SDP structure. * Avoided adding duplicate files p4verify.pl, p4review.(py,cfg). * Upgraded 'dist.sh' utility to produce both Unix and Windows packages (*.tgz and *.zip), adjusting line endings on text files to be appropriate for Windows prior to packaging. To Do: * Resolve duplication of [template_]configure_new_server.bat. * Merge test suites for Windows and Unix into a cohesive set. |