# HG changeset patch # User Jan Damborsky # Date 1223453612 -7200 # Node ID 9a75efc4064fae0cf946082cb6121ff42801ccc5 # Parent 8a1045ae9dd9d1ce91c109948ee7ba2aa56c685c 3478 Automated Installer needs support for discovery of valid install services diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/Makefile.master --- a/usr/src/Makefile.master Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/Makefile.master Wed Oct 08 10:13:32 2008 +0200 @@ -130,6 +130,7 @@ ROOTPYTHONVENDORINSTALL= $(ROOTPYTHONVENDOR)/osol_install ROOTPYTHONVENDORINSTALLBEADM= $(ROOTPYTHONVENDORINSTALL)/beadm ROOTPYTHONVENDORINSTALLDC= $(ROOTPYTHONVENDORINSTALL)/distro_const +ROOTPYTHONVENDORINSTALLAI= $(ROOTPYTHONVENDORINSTALL)/auto_install ROOTDC= $(ROOT)/usr/share/distro_const ROOTDC_SLIM= $(ROOTDC)/slim_cd ROOTDC_AI= $(ROOTDC)/auto_install diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/Targetdirs --- a/usr/src/Targetdirs Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/Targetdirs Wed Oct 08 10:13:32 2008 +0200 @@ -86,6 +86,7 @@ /usr/lib/python2.4/vendor-packages/osol_install \ /usr/lib/python2.4/vendor-packages/osol_install/beadm \ /usr/lib/python2.4/vendor-packages/osol_install/distro_const \ + /usr/lib/python2.4/vendor-packages/osol_install/auto_install \ /usr/dt \ /usr/dt/bin \ /usr/dt/appconfig \ diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/cmd/Makefile.targ --- a/usr/src/cmd/Makefile.targ Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/cmd/Makefile.targ Wed Oct 08 10:13:32 2008 +0200 @@ -111,6 +111,9 @@ $(ROOTPYTHONVENDORINSTALLDC): $(INS.dir) +$(ROOTPYTHONVENDORINSTALLAI): + $(INS.dir) + # # Python .py and .pyc files need to be installed with the original # timestamp of the file preserved. Otherwise, .pyc files will @@ -127,6 +130,9 @@ $(ROOTPYTHONVENDORINSTALLDC)/%: % $(CP_P.file) +$(ROOTPYTHONVENDORINSTALLAI)/%: % + $(CP_P.file) + $(ROOTDC): $(INS.dir) diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/cmd/auto-install/Makefile --- a/usr/src/cmd/auto-install/Makefile Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/cmd/auto-install/Makefile Wed Oct 08 10:13:32 2008 +0200 @@ -31,9 +31,9 @@ clobber:= TARGET= clobber install:= TARGET= install -PROGS= ai_get_manifest +PROGS= ai_get_manifest ai_sd -PYMODULES= +PYMODULES= __init__.py PYCMODULES= $(PYMODULES:%.py=%.pyc) @@ -63,6 +63,10 @@ $(CP) ai_get_manifest.py ai_get_manifest $(CHMOD) 755 ai_get_manifest +ai_sd: ai_sd.py + $(CP) ai_sd.py ai_sd + $(CHMOD) 755 ai_sd + install_h: include ../Makefile.targ diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/cmd/auto-install/README.test --- a/usr/src/cmd/auto-install/README.test Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/cmd/auto-install/README.test Wed Oct 08 10:13:32 2008 +0200 @@ -75,3 +75,66 @@ # ./ai_get_manifest -s -o -d 1-4 where '-d 4' enables the most verbose mode + +AI service discovery engine (AISD) +================================== +Name of executable: /usr/bin/ai_sd +Package: SUNWauto-install +Implementation: Python script + +Purpose: Tries to look up AI service which could provide AI and System Configuration +(SC) combined manifest valid for the client. Please refer to AI design specification +for more details - it can be obtained from Mercurial workspace +ssh://anon@hg.opensolaris.org/hg/caiman/caiman-docs - AI/ai_design_doc.odt file + +Usage: +ai_sd [-s service_type] [-n service_name] [-t timeout] [-d debug_level] [-h] -o discovered_services_file + +Input: Service name to look up, timeout +Output: List of AI server providing the service in format
: + +[1] Test of looking up 'named' service +-------------------------------------- +* Prerequisites: + -none + +* Test procedure +[a] Publish service '_test_service' using dns-sd(1M) command +$ dns-sd -R _test_service _OSInstall._tcp local 8081 aiwebserver=tio:8081 + +[b] Run AISD to look up the service '_test-service' +$ ai_sd -n _test_service -o + +* Expected output + file containing address of AI server in form +'tio:8081' + +* Return codes +0 - success +2 - service not found + +[2] Test of looking up 'default' service +---------------------------------------- +* Prerequisites: + -none + +* Test procedure +[a] Publish service '_default' using dns-sd(1M) command +$ dns-sd -R _default _OSInstall._tcp local 8081 aiwebserver=tio:8081 + +[b] Run AISD to look up the service '_default' +$ ai_sd -o + +* Expected output + file containing address of AI server in form +'tio:8081' + +* Return codes +0 - success +2 - service not found + +[3] Run with increased debug verbosity +-------------------------------------- +$ ai_sd -n -o -d 1-4 + +where '-d 4' enables the most verbose mode diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/cmd/auto-install/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/auto-install/__init__.py Wed Oct 08 10:13:32 2008 +0200 @@ -0,0 +1,32 @@ +#!/usr/bin/python +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. + +# +# This file is installed into +# usr/lib/python2.4/vendor-packages/osol_install/auto_install/ directory +# and lets the Python interpreter know that this directory contains valid +# Python modules which can be imported using following command: +# from osol_install.auto_install. import +# diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/cmd/auto-install/ai_sd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/auto-install/ai_sd.py Wed Oct 08 10:13:32 2008 +0200 @@ -0,0 +1,428 @@ +#!/usr/bin/python +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ai_sd - AI Service Discovery Engine +# + +import getopt +import os +import re +import signal +import string +import sys +import traceback +from subprocess import * +from osol_install.auto_install.ai_get_manifest import ai_log + +# +# ID of process running dns-sd(1M) command. It will be used by os.kill() +# to terminate the process. This is global variable for now, so only one +# dns-sd(1M) command can be run at a time. +# +# TODO: Needs to be modified if it turns out, that it would be usefull +# to have more dns-sd(1M) running at once +# +sd_pid = -1 + +# +# Flag which indicates that timer expired for running dns-sd(1M) +# This is global variable for now, so only one dns-sd(1M) +# command can be run at a time. +# +# TODO: Needs to be modified if it turns out, that it would be usefull +# to have more dns-sd(1M) running at once +# +sd_timeout_expired = False + +# +# AI service discovery logging service +# +aisd_log = ai_log("AISD") + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def svc_lookup_timeout_handler(signum, frame): + global sd_pid + global sd_timeout_expired + + """ Function: svc_lookup_timeout_handler() + + Description: + Handles raising of SIGALRM signal - kill the process + with ID saved in sd_pid by sending SIGTERM signal + + Parameters: + signum - number of signal + frame - current stack frame + + Returns: + + """ + + if sd_pid >= 0: + os.kill(sd_pid, signal.SIGTERM) + + # set the flag and disable the alarm + sd_timeout_expired = True + signal.alarm(0) + +# +# ai_service class - base class for holding/manipulating AI service +# +class ai_service: + # service type + type = '_OSInstall._tcp' + + def __init__(self, name = "_default", timeout = 5, domain = "local"): + """ Metod: __init__ + + Parameters: + name - name of the service instance + timeout - max time to lookup the service + domain - .local for multicast DNS + + Returns: + True..service found, False..service not found + + """ + self.name = name + self.timeout = timeout + self.domain = domain + self.found = False + self.svc_info = self.svc_txt_rec = None + return + + def found(self): + """ Metod: found + + Returns: + True..service found, False..service not found + + """ + return self.found + + def get_txt_rec(self): + """ Metod: get_txt_rec + + Returns: + Service TXT record + + """ + return self.svc_txt_rec + + def lookup(self): + global sd_pid + global sd_timeout_expired + + """ Metod: lookup + + Description: + Tries to look up service instance + + Returns: + 0..service found, -1..service not found + + """ + + # + # discard the information we obtained so far + # and start new look up process + # + self.svc_info = self.svc_txt_rec = None + self.found = False + + # dns-sd(1M) is used for look up the service + cmd = "/usr/bin/dns-sd -L %s %s %s" % (self.name, \ + self.__class__.type, self.domain) + + aisd_log.post(ai_log.AI_DBGLVL_INFO, "cmd: %s", cmd) + + cmd_args = cmd.split() + + # + # spawn new process which will take care of looking up + # the service. If the service is not found within specified + # time, raise the SIGALRM signal and kill the process + # + try: + cmd_popen = Popen(cmd_args, stdout=PIPE, stderr=PIPE) + + except OSError: + aisd_log.post(ai_log.AI_DBGLVL_ERR, \ + "Popen() raised OSError exception") + + return -1 + + except ValueError: + aisd_log.post(ai_log.AI_DBGLVL_ERR, \ + "Popen() raised ValueError exception") + + return -1 + + # + # save ID of new process - os.kill() will use it later + # to terminate the process + # + sd_pid = cmd_popen.pid + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "dns-sd pid: %d", sd_pid) + + signal.signal(signal.SIGALRM, svc_lookup_timeout_handler) + + # activate timeout + sd_timeout_expired = False + signal.alarm(self.timeout) + + # + # Save file descriptors for stdout, stderr + # from dns-sd for later purposes + # + cmd_stdout_fd = cmd_popen.stdout + cmd_stderr = None + + svc_info = svc_txt_rec = None + svc_info_found = svc_txt_rec_found = False + + # + # process output from dns-sd(1M) until either service + # is found or timeout expires + # + while cmd_popen.poll() == None and not sd_timeout_expired: + try: + # read main service info - wait for the line + # containing service name + line = cmd_stdout_fd.readline().strip() + + # search for exact match of given service name + svc = line.split() + if len(svc) > 1 and re.search( \ + "^%s." % self.name, svc[1].strip()): + + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + " Svc: %s", line) + + svc_info_found = True + svc_info = line + svc_txt_rec_found = False + continue + + # + # read and verify TXT records - + # following format is expected: + # + # aiwebserver=
: + # + if re.search(r"^aiwebserver=", line): + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + " TXT: %s", line) + svc_txt_rec = line + svc_txt_rec_found = True + else: + svc_info_found = \ + svc_txt_rec_found = False + + # + # Abort look up process, if service was found + # + + if svc_info_found and svc_txt_rec_found: + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + " %s service found", self.name) + break; + + # + # IOError exception is raised when the process + # is terminated during I/O operation. This will + # happen if service can't be found, since in this case + # we would be waiting in cmd_stdout_fd.readline() + # + except IOError: + # wait for process to finish + (cmd_stdout, cmd_stderr) = \ + cmd_popen.communicate() + + if sd_timeout_expired: + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "Timeout expired, dns-sd (pid=%d)" \ + " terminated", sd_pid) + + else: + aisd_log.post(ai_log.AI_DBGLVL_WARN, \ + "IOError raised for unknown reason") + + # disable alarm + signal.alarm(0) + + # + # if the process didn't finish yet, it is either + # still running or in the phase of finishing its job. + # + if cmd_popen.poll() == None: + # If process is running, terminate it + if not sd_timeout_expired: + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "dns-sd (pid=%d) is running, will be " \ + "terminated", sd_pid) + + os.kill(sd_pid, signal.SIGTERM) + + if cmd_popen.poll() != None: + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "dns-sd return code: %d", cmd_popen.returncode) + + # capture output of stderr for debugging purposes + if cmd_stderr != None and cmd_stderr != "": + aisd_log.post(ai_log.AI_DBGLVL_WARN, \ + " stderr: %s", cmd_stderr) + + if svc_info_found and svc_txt_rec_found: + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "Valid service found:\n svc: %s\n TXT: %s", \ + svc_info, svc_txt_rec) + + self.found = True + self.svc_info = svc_info + self.svc_txt_rec = svc_txt_rec + return 0 + + return -1 + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def usage(): + print >> sys.stderr, ("Usage:\n" \ + " ai_sd -s service type -n service_name " \ + "-o discovered_services_file -t timeout [-d debug_level] [-h]") + sys.exit(1) + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def parse_cli(cli_opts_args): + rc = 0; + + if len(cli_opts_args) == 0: + usage() + + opts_args = cli_opts_args[1:] + + try: + opts, args = getopt.getopt(opts_args, "t:s:n:o:d:h") + except getopt.GetoptError: + aisd_log.post(ai_log.AI_DBGLVL_ERR, \ + "Invalid options or arguments provided") + usage() + + service_name = "" + service_lookup_timeout = 5 + + service_file = "/tmp/service_list" + + for o, a in opts: + if o == "-s": + ai_service.type = a + if o == "-n": + service_name = a + elif o == "-o": + service_file = a + elif o == "-t": + service_lookup_timeout = int(a) + elif o == "-d": + aisd_log.set_debug_level(int(a)) + elif o == "-h": + usage() + + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "Service file: %s", service_file) + + service_list = [] + + # + # if service name was specified, add it to the list + # of services to be looked up + # + if service_name != "": + service_list.append(ai_service(service_name, \ + service_lookup_timeout)) + + # add default service + service_list.append(ai_service('_default', service_lookup_timeout)) + + # Go through the list of services and try to look up them + for i in range(len(service_list)): + svc_instance = "%s.%s.%s" % (service_list[i].name, \ + ai_service.type, service_list[i].domain) + + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "Service to look up: %s", svc_instance) + + # look up the service + ret = service_list[i].lookup() + if ret == 0: + break; + + if ret == -1: + aisd_log.post(ai_log.AI_DBGLVL_ERR, \ + "No valid AI service found") + return 2 + + # + # parse information captured from dns-sd in order + # to obtain source of service (address and port) + # extract value from 'aiwebserver' name-value pair + # + svc_address = service_list[i].get_txt_rec(). \ + strip().split('aiwebserver=', 1)[1].split(',')[0] + (svc_address, svc_port) = svc_address.split(':') + + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "%s can be reached at: %s:%s", svc_instance, \ + svc_address, svc_port) + + # write the information to the given location + aisd_log.post(ai_log.AI_DBGLVL_INFO, \ + "Storing service list into %s", service_file) + + try: + fh_svc_list = open(service_file, 'w') + except IOError: + aisd_log.post(ai_log.AI_DBGLVL_ERR, \ + "Couldn't open %s for saving service list", service_file) + return 2 + + fh_svc_list.write("%s:%s\n" % (svc_address, svc_port)) + fh_svc_list.close() + + return 0 + + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if __name__ == "__main__": + try: + rc = parse_cli(sys.argv) + except SystemExit, e: + raise e + except: + traceback.print_exc() + sys.exit(1) + sys.exit(rc) diff -r 8a1045ae9dd9 -r 9a75efc4064f usr/src/pkgdefs/SUNWauto-install/prototype_com --- a/usr/src/pkgdefs/SUNWauto-install/prototype_com Tue Oct 07 14:28:42 2008 -0600 +++ b/usr/src/pkgdefs/SUNWauto-install/prototype_com Wed Oct 08 10:13:32 2008 +0200 @@ -36,3 +36,16 @@ d none usr 755 root sys d none usr/bin 755 root bin f none usr/bin/ai_get_manifest 555 root bin +f none usr/bin/ai_sd 555 root bin + +# +# Automated Installer python modules +# +d none usr/lib 0755 root bin +d none usr/lib/python2.4 0755 root bin +d none usr/lib/python2.4/vendor-packages 0755 root bin +d none usr/lib/python2.4/vendor-packages/osol_install 0755 root bin +d none usr/lib/python2.4/vendor-packages/osol_install/auto_install 0755 root bin +f none usr/lib/python2.4/vendor-packages/osol_install/auto_install/__init__.py 0444 root bin +f none usr/lib/python2.4/vendor-packages/osol_install/auto_install/__init__.pyc 0444 root bin +s none usr/lib/python2.4/vendor-packages/osol_install/auto_install/ai_get_manifest.py=../../../../../bin/ai_get_manifest