# SDPEnv.py
# Utilities for creating or validating an environment based on a master configuration file
#------------------------------------------------------------------------------
# Copyright (c) Perforce Software, Inc., 2007-2014. All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE
# SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#------------------------------------------------------------------------------
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
# 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"
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 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(":")
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["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:
valid_service_types = "standard replica forwarding-replica build-server".split()
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
replica_types = "replica forwarding-replica build-server".split()
if instance.sdp_service_type in 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"] == "standard":
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
mailfrom maillist mailhost
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 ["replica", "forwarding-replica", "build-server"]:
lines.append("remote_sdp_instance=%s" % (master_instance.sdp_instance))
lines.append("p4target=%s:%s" % (master_instance.sdp_hostname,
master_instance.sdp_p4port_number))
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))
for line in [x.strip() for x in templateLines]:
if line:
newLine = line.replace("p4 configure set", p4configurecmd)
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"]:
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#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))
cmd_lines[master_id].append('%s "%s#startup.1=pull -i 1"' % (p4configurecmd, instance.sdp_serverid))
for i in range(2, 6):
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))
if instance.sdp_service_type in ["replica", "build-server", "forwarding-replica"]:
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 = []
for file_pair in files_to_copy_list:
src, dest = file_pair
if files_different(src, dest):
if src not in files_copied:
files_copied.append(src)
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', 'sync_replica.bat',
'weekly_backup.bat', 'weekly_sync_replica.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.common_bin_dir, instance.admin_pass_filename)
with open(admin_pass_filename, "w") as fh:
fh.write(instance.sdp_p4superuser_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, 'instsrv.exe')
cmd = '%s p4_%s "%s"' % (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-2014 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('@call %s %s\n' % (fname, 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."
sdpconfig = SDPConfig()
try:
sdpconfig.process_config()
except SDPException as e:
print(str(e))
if __name__ == '__main__':
main()
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #11 | 28240 | C. Thomas Tyler |
Released SDP 2021.1.28238 (2021/11/12). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
| #10 | 27331 | C. Thomas Tyler |
Released SDP 2020.1.27325 (2021/01/29). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
| #9 | 26161 | C. Thomas Tyler |
Released SDP 2019.3.26159 (2019/11/06). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
| #8 | 21606 | Robert Cowham | Propagate Windows changes - reinstate instance specific .bat files. | ||
| #7 | 21141 | Robert Cowham |
Make sure server.depot.root is set. Don't create instance bat files. |
||
| #6 | 20767 | C. Thomas Tyler |
Released SDP 2016.2.20755 (2016/09/29). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
| #5 | 15856 | C. Thomas Tyler |
Replaced the big license comment block with a shortened form referencing the LICENSE file included with the SDP package, and also by the URL for the license file in The Workshop. |
||
| #4 | 15636 | Robert Cowham |
Make it really obvious that DEFAULT_HOSTNAME must be changed in sdp_master_config.ini Insert check for not being changed, and test it. |
||
| #3 | 11716 | Robert Cowham |
Ensure that p4d.exe is properly copies as p4s.exe for services in the install_*.bat Fix the setting of SDP counter. |
||
| #2 | 11039 | Robert Cowham |
Removed duplicated (and out of date) SDPEnv.py from Server setup. Use a README.txt to point to the Windows specific version of these files. Move reporting function (which requires P4Python) from SDPEnv.py to report_env.py - makes it standalone. |
||
| #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. |