├── .travis.yml ├── distrib └── linux │ └── ccontainermain ├── Makefile ├── dockerfile └── Caché+SSH │ ├── ccontrol-wrapper.sh │ └── Dockerfile ├── LICENSE ├── README.md └── ccontainermain.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | install: 4 | - make get-deps 5 | 6 | script: 7 | - make 8 | -------------------------------------------------------------------------------- /distrib/linux/ccontainermain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intersystems-community/ccontainermain/master/distrib/linux/ccontainermain -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for a project 2 | 3 | BINARY=ccontainermain 4 | 5 | OS=linux 6 | ARCH=amd64 7 | LDFLAGS= 8 | 9 | all: build 10 | build: 11 | env GOOS=${OS} GOARCH=${ARCH} go build ${LDFLAGS} -o distrib/${OS}/${BINARY} ccontainermain.go 12 | 13 | get-deps: 14 | go get github.com/hpcloud/tail 15 | -------------------------------------------------------------------------------- /dockerfile/Caché+SSH/ccontrol-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Work around a werid overlayfs bug where files don't open properly if they haven't been 4 | # touched first - see the yum-ovl plugin for a similar workaround 5 | if [ "${1,,}" == "start" ]; then 6 | find / -name CACHE.DAT -exec touch {} \; 7 | fi 8 | 9 | /usr/local/etc/cachesys/ccontrol $@ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 InterSystems Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dockerfile/Caché+SSH/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Docker manifest file builds a container with: 2 | # - sshd running (linux containers don't usually have it) 3 | # - Caché 2016.2 and 4 | # - it handles container PID 1 via ccontainermain which offers various flags 5 | # 6 | # build the new image with: 7 | # $ docker build --force-rm --no-cache -t cache:2016.2 . 8 | #-- 9 | 10 | # pull from this repository 11 | # note that if you don't have the distribution you're after it will be automatically 12 | # downloaded from Docker central hub repository (you'll have to create a user there) 13 | # 14 | FROM tutum/centos:latest 15 | 16 | MAINTAINER user 17 | 18 | # setup vars section___________________________________________________________________ 19 | # 20 | ENV TMP_INSTALL_DIR=/tmp/distrib 21 | 22 | # vars for Caché silent install 23 | ENV ISC_PACKAGE_INSTANCENAME="CACHE" 24 | ENV ISC_PACKAGE_INSTALLDIR="/usr/cachesys" 25 | ENV ISC_PACKAGE_UNICODE="Y" 26 | 27 | # Caché distribution file________________________________________________________________ 28 | # set-up and install Caché from distrib_tmp dir 29 | RUN mkdir ${TMP_INSTALL_DIR} 30 | WORKDIR ${TMP_INSTALL_DIR} 31 | 32 | # update OS + dependencies & run Caché silent install___________________________________ 33 | RUN yum -y update && \ 34 | yum -y install tar hostname net-tools which wget java && \ 35 | 36 | # Replace the following location with that of your Cache 2016.2 kit 37 | wget -O - 'https://replace_this_with_your_server/distrib/cache-2016.2.0.736.0-lnxrhx64.tar.gz' \ 38 | 39 | # Alternatively, if you're comfortable with all parties with access to to this docker image having 40 | # access to these WRC credentials via the `docker history` command, comment out the above line, 41 | # uncomment the following lines and fill in your WRC_USERNAME and WRC_PASSWORD to automatically 42 | # fetch the kit from InterSystems' WRC. 43 | 44 | # WRC_USERNAME="user@company.com" && \ 45 | # WRC_PASSWORD="your_password_here" && \ 46 | # wget -qO /dev/null --keep-session-cookies --save-cookies /dev/stdout --post-data="UserName=$WRC_USERNAME&Password=$WRC_PASSWORD" 'https://login.intersystems.com/login/SSO.UI.Login.cls?referrer=https%253A//wrc.intersystems.com/wrc/login.csp' \ 47 | # | wget -O - --load-cookies /dev/stdin 'https://wrc.intersystems.com/wrc/WRC.StreamServer.cls?FILE=/wrc/distrib/cache-2016.2.0.736.0-lnxrhx64.tar.gz' \ 48 | 49 | | tar xvfzC - . && \ 50 | ./cache-*/cinstall_silent && \ 51 | rm -rf ${TMP_INSTALL_DIR}/* && \ 52 | ccontrol stop $ISC_PACKAGE_INSTANCENAME quietly 53 | COPY cache.key $ISC_PACKAGE_INSTALLDIR/mgr/ 54 | 55 | # Workaround for an overlayfs bug which prevents Cache from starting with errors 56 | COPY ccontrol-wrapper.sh /usr/bin/ 57 | RUN cd /usr/bin && \ 58 | rm ccontrol && \ 59 | mv ccontrol-wrapper.sh ccontrol && \ 60 | chmod 555 ccontrol 61 | 62 | # TCP sockets that can be accessed if user wants to (see 'docker run -p' flag) 63 | EXPOSE 57772 1972 22 64 | 65 | # Caché container main process PID 1 (https://github.com/zrml/ccontainermain) 66 | WORKDIR / 67 | ADD ccontainermain . 68 | 69 | ENTRYPOINT ["/ccontainermain"] 70 | 71 | # run via: 72 | # docker run -d -p 57772:57772 -p 2222:22 -e ROOT_PASS="linux" -i=CACHE -xstart=/run.sh 73 | # 74 | # more options & explinations 75 | # $ docker run -d // detached in the background; accessed only via network 76 | # --privileged // only for kernel =<3.16 like CentOS 6 & 7; it gives us root privileges to tune the kernel etc. 77 | # -h // you can specify a host name 78 | # -p 57772:57772 // TCP socket port mapping as host_external:container_internal 79 | # -p 0.0.0.0:2222:22 // this means allow 2222 to be accesses from any ip on this host and map it to port 22 in the container 80 | # -e ROOT_PASS="linux" // -e for env var; tutum/centos extension for root pwd definition 81 | # // see docker images to fetch the right name & tag or id 82 | # // after the Docker image id, we can specify all the flags supported by 'ccontainermain' 83 | # // see this page for more info https://github.com/zrml/ccontainermain 84 | # -i=CACHE // this is the Cachè instance name 85 | # -xstart=/run.sh // eXecute another service at startup time 86 | # // run.sh starts sshd (part of tutum centos container) 87 | # // for more info see https://docs.docker.com/reference/run/ 88 | # 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ccontainermain 2 | 3 | The program `ccontainermain` allows a Caché, Ensemble or HealthShare product to run in a Docker container. 4 | Docker containers need a PID 1 or main process to hold up the container. This is what `ccontainermain` provides. 5 | It is developed so that one can quickly work with Caché in a Docker container vs 6 | * having to understand why the container dies straight away and 7 | * having to develop a comprehensive script. 8 | 9 | The name convention used is that of InterSystems commands found in the /bin directory like: 10 | ccontrol, cstart, cforce etc. 11 | 12 | `ccontainermain` is called to run as the main process of a Docker container. 13 | One would copy it in the container and specify it as program to run when issuing a 'docker run' command. Alternatively, when using a dockerfile manifesto you would specify `ccontainermain` as the ENTRYPOINT command argument. See Docker documentation on Dockerfile declarative manifesto. The typical string found at the end of a Dockerfile would look like: 14 | 15 | `ENTRYPOINT ["/ccontainermain"]` 16 | 17 | Please see the dockerfile examples in the [dockerfile] folder above. 18 | 19 | `ccontainermain` start Caché|Ensemble|HealthShare and logs any message and issues to the standard Docker logs output. 20 | It also tries to tune shared memory so that Caché may start. You can pass higher value than the default 512MB that is usually enough to work. 21 | 22 | `ccontainermain` also allows a software developer to start her or his Caché program and also other services. 23 | Here is an example of the syntax on how to fire up a container created via a Dockerfile -with the ENTRYPOINT specified as above, which wraps the call to a java service needed for the app: 24 | 25 | `$ docker run -p 57772:57772 centos7:C153 -xstart=/myExtraService.sh` 26 | 27 | However, the most important thing that ccontainermain does is probably the trapping of signals to the container. 28 | Consider the Docker command: 29 | 30 | $ docker stop 31 | 32 | Docker gives a 10 seconds default and then bring the container down. Not ideal for a database using shared memory. 33 | `ccontainermain` traps the signal and runs the Caché silent shutdown. Please remember to specify the -t (timeout) flag to the Docker stop command with a value >10 seconds as at times -depending on how busy the system is, it takes longer than that. Of course it all depends if one uses volumes or if one just uses a DB inside the container as an immutable artifact. 34 | 35 | ## options 36 | `ccontainermain` offers several flags: 37 | * -i for instance; it allows to specify the DB instance to start & stop; -i=CACHESYS2 38 | * -n for namespace; it allows to specify the namespace where to run a program; -n=USER 39 | * -r for routine; it allows to specify the routine name to start; -r=myApp or -r="##class(package.class).method()" 40 | * -shmem for tuning SHMMAX; default val 512MB; -shmem=1024 41 | * -xstart for eXecuting anything before Caché is started; -xstart=/myapp/runMyExtraServive.sh 42 | * -xstop for eXecuting anything after the Caché shutdown; -xstop=/bringAllMyProcsDown.sh 43 | * -cstart it's a boolean defaulted to true; It gives us the option to start a container without starting Caché; -cstart=true 44 | * -cstop it's a boolean defaulted to true; it gives the option to skip the Caché shutdown; -cstop=false 45 | * -nostu it's a boolean defaulted to false; it allows DB single user startup for maintenance mode 46 | * -cconsole it's a boolean defaulted to false; it shows Caché cconsole.log output in the container logs output 47 | * -version shows ccontainer version 48 | 49 | The above flags can also be retrieved via 50 | 51 | $ ./ccontainermain -help 52 | 53 | 54 | For more information on the Caché `ccontrol` related options please see: 55 | [InteSystems documentation] (http://docs.intersystems.com/cache201511/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_using_instance#GSA_using_instance_control) 56 | and for the rest see 57 | [the Docker documentation] (https://docs.docker.com/) 58 | 59 | Please note that I've left in a debug (dbg) constant that you can use to get extra debugging information throughout the program. 60 | For your convenience a linux executable has been provided so that you don't have to install GO and compile the code. 61 | If you have GO installed simply 62 | 63 | $ go build ccontainermain.go 64 | 65 | 66 | Please also note the dockerfile/ directory under which I'll try to upload few useful Dockerfile examples. Dockerfiles are Docker engine manifests that allows one to automate Docker images creation. 67 | 68 | HTH 69 | 70 | 71 | ## TODO 72 | * investigate SIGCHLD for dying processes and clean up PID table 73 | * Windows/Azure support and testing 74 | 75 | -------------------------------------------------------------------------------- /ccontainermain.go: -------------------------------------------------------------------------------- 1 | /* 2 | +--------------------------------------------------------------------------------+ 3 | | 2015-05 Luca R - initially created to support a containerized Caché | 4 | | | 5 | | | 6 | +--------------------------------------------------------------------------------+ 7 | */ 8 | 9 | package main 10 | 11 | import ( 12 | "bytes" 13 | "errors" 14 | "flag" 15 | "fmt" 16 | "log" 17 | "os" 18 | "os/exec" 19 | "os/signal" 20 | "runtime" 21 | "strconv" 22 | "strings" 23 | "syscall" 24 | 25 | "github.com/hpcloud/tail" 26 | ) 27 | 28 | const ( 29 | version = "0.6" 30 | dbg = false 31 | k316 = 3.16 // the kernel version that allows containers to use more useful ssmmmax seg value 32 | pre316MaxShmall = 8192 33 | ) 34 | 35 | // setting shmmax 36 | // because the linux default in kernel =3.16 49 | // in pre-3.16 SHMALL is 8192MB so one single segment (shmmax) cannot be larger than that 50 | if shmmaxVal > pre316MaxShmall { 51 | log.Printf("Warning: std pre-3.16 linux kernel has only 8192MB of total shared memory (shmall); Setting shmmax to this limit (8GB)") 52 | shmmaxVal = pre316MaxShmall 53 | } 54 | 55 | shmmaxByteVal := (shmmaxVal * 1024 * 1024) 56 | if dbg { 57 | log.Printf("shmmaxByteVal: %d", shmmaxByteVal) 58 | } 59 | 60 | // concatenating the cmd string 61 | kstr := []string{"kernel.shmmax=", strconv.Itoa(shmmaxByteVal)} 62 | kernelParamShmmax := strings.Join(kstr, "") 63 | if dbg { 64 | log.Printf("kernelParamShmmax: %s", kernelParamShmmax) 65 | } 66 | 67 | // the OS command to set the new shmmax kernel param 68 | cmd := "sysctl" 69 | args := []string{"-w", kernelParamShmmax} 70 | 71 | // and its execution 72 | if err := exec.Command(cmd, args...).Run(); err != nil { 73 | log.Printf("Error & possible causes:\n") 74 | log.Printf("-insufficient privileges to run sysctl; you must be on < 3.16 kernel\n") 75 | log.Printf("-container running without --privileged flag (needed for setting shared mem)\n") 76 | log.Printf("ERR: %s", err) 77 | os.Exit(1) 78 | } 79 | 80 | return true, nil 81 | } 82 | 83 | // returns instalation folder for given instance 84 | func getInstanceFolder(inst string) string { 85 | var folder string 86 | cmd := "ccontrol" 87 | args := []string{"qlist", inst} 88 | 89 | // Output runs the command and returns its standard output 90 | if out, err := exec.Command(cmd, args...).Output(); err != nil { 91 | log.Printf("Error while getting Cachéinstallation folders\n") 92 | log.Printf("ERR: %s.", err) 93 | os.Exit(1) 94 | 95 | } else { 96 | // ccontrol qlist string examples: 97 | // C151^/usr/cachesys^2015.1.0.429.0^running, since Mon Jun 8 12:00:30 2015^cache.cpf^1972^57772^62972^warn^ 98 | // CACHE142^/Users/CACHE142^2014.2.0.177.0^sign-on inhibited, last used Mon Jun 8 11:31:37 2015^cache.cpf^1972^57772^62972^ 99 | // C151^/usr/cachesys^2015.1.0.429.0^down, last used Mon Jun 8 16:40:07 2015^cache.cpf^1972^57772^62972^^ 100 | qlistStr := string(out) 101 | if qlistStr == "" { 102 | log.Printf("Error: Cannot continue as qlistStr from 'ccontrol qlist ' is empty") 103 | os.Exit(1) 104 | } 105 | 106 | // parsing the returned string; they're all []string... 107 | folder = strings.SplitN(qlistStr, "^", 4)[1] 108 | } 109 | 110 | return folder 111 | } 112 | 113 | // shows all new lines in cconsole.log 114 | func tailCConsoleLog(inst string) { 115 | folder := getInstanceFolder(inst) 116 | endLocation := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} 117 | if t, err := tail.TailFile(folder+"/mgr/cconsole.log", tail.Config{Follow: true, Location: &endLocation}); err != nil { 118 | log.Printf("Error while getting content for cconsole.log\n") 119 | log.Printf("ERR: %s.\n", err) 120 | } else { 121 | for line := range t.Lines { 122 | fmt.Println(line.Text) 123 | } 124 | } 125 | } 126 | 127 | // starting Caché 128 | // 129 | func startCaché(inst string, nostu bool, cclog bool, fail bool) (bool, error) { 130 | log.Printf("Starting Caché...\n") 131 | 132 | // building the start string 133 | cmd := "ccontrol" 134 | args := []string{"start"} 135 | args = append(args, inst) 136 | if nostu == true { 137 | args = append(args, "nostu") 138 | } 139 | args = append(args, "quietly") 140 | 141 | if dbg { 142 | log.Printf("Caché start cmd: %s %q", cmd, args) 143 | } 144 | 145 | if cclog { 146 | go tailCConsoleLog(inst) 147 | } 148 | 149 | c := exec.Command(cmd, args...) 150 | 151 | // preparing for stdout/stderr msg 152 | var out bytes.Buffer 153 | c.Stdout = &out 154 | 155 | // run it 156 | if err := c.Run(); err != nil { 157 | errMsg := out.String() 158 | log.Printf("Error & possible causes:\n") 159 | log.Printf("-Caché was not installed successfully") 160 | log.Printf("-wrong Caché instance name") 161 | log.Printf("-missing privileges to start/stop Caché; proc not in Caché group.") 162 | log.Printf("ERR: %s; %s", err, errMsg) 163 | os.Exit(1) 164 | } 165 | 166 | // check that the start-up was successful 167 | if err := checkCmdOutcome("up", inst, fail); err != nil { 168 | log.Printf("Error: Caché was not brought up successfully.\n") 169 | os.Exit(1) 170 | } 171 | 172 | return true, nil 173 | } 174 | 175 | // check if cstart or cstop were successfull 176 | // what = what we are checking: 177 | // "up" checks for successful Caché start-up 178 | // "down" checks for successful Caché shutdown 179 | // 180 | func checkCmdOutcome(what string, inst string, fail bool) error { 181 | cmd := "ccontrol" 182 | args := []string{"qlist", inst} 183 | 184 | // Output runs the command and returns its standard output 185 | if out, err := exec.Command(cmd, args...).Output(); err != nil { 186 | log.Printf("Error while verifying Caché '%s' status\n", what) 187 | log.Printf("ERR: %s.", err) 188 | os.Exit(1) 189 | 190 | } else { 191 | // ccontrol qlist string examples: 192 | // C151^/usr/cachesys^2015.1.0.429.0^running, since Mon Jun 8 12:00:30 2015^cache.cpf^1972^57772^62972^warn^ 193 | // CACHE142^/Users/CACHE142^2014.2.0.177.0^sign-on inhibited, last used Mon Jun 8 11:31:37 2015^cache.cpf^1972^57772^62972^ 194 | // C151^/usr/cachesys^2015.1.0.429.0^down, last used Mon Jun 8 16:40:07 2015^cache.cpf^1972^57772^62972^^ 195 | qlistStr := string(out) 196 | if qlistStr == "" { 197 | log.Printf("Error: Cannot continue as qlistStr from 'ccontrol qlist ' is empty") 198 | os.Exit(1) 199 | } 200 | 201 | // parsing the returned string; they're all []string... 202 | upDownStr := strings.SplitN(qlistStr, "^", 4) 203 | 204 | // we are only interested in the 1st word: "running", "down" etc. 205 | CachéStatus := strings.SplitN(upDownStr[3], ",", 2) 206 | cstatus := CachéStatus[0] 207 | 208 | if cstatus == "running" { 209 | log.Printf("Caché started successfully\n") 210 | 211 | } else if cstatus == "down" { 212 | log.Printf("Caché stopped successfully\n") 213 | 214 | } else if cstatus == "sign-on inhibited" { 215 | log.Printf("Something is preventing Caché from starting in multi-user mode,\n") 216 | log.Printf("You might want to start the container with the flag -cstart=false to fix it.\n") 217 | 218 | if fail == true { 219 | return errors.New("sign-on inhibited") 220 | } 221 | } else { 222 | log.Printf("Un-recognized Caché status while trying to verify its '%s' status\n", what) 223 | log.Printf("-qlist string = %s.\n", qlistStr) 224 | } 225 | } 226 | 227 | return nil 228 | } 229 | 230 | // starting Caché app by calling a routine or class method 231 | // 232 | func startApp(inst string, nmsp string, rou string) (bool, error) { 233 | log.Printf("Starting app '%s' in '%s'...\n", rou, nmsp) 234 | 235 | cmd := "ccontrol" 236 | args := []string{"session", inst, "-U", nmsp, rou} 237 | c := exec.Command(cmd, args...) 238 | 239 | if dbg { 240 | log.Printf("cmd & args: %s, %q", cmd, args) 241 | } 242 | 243 | // preapring for stdout/stderr msg 244 | var out bytes.Buffer 245 | c.Stdout = &out 246 | 247 | if err := c.Run(); err != nil { 248 | errMsg := out.String() 249 | log.Printf("Error in launching routine %s in namespace %s:", rou, nmsp) 250 | log.Printf("Err: %s; %s", err, errMsg) 251 | os.Exit(1) 252 | } 253 | 254 | return true, nil 255 | } 256 | 257 | // Stopping Caché 258 | // 259 | func shutdownCaché(inst string) (bool, error) { 260 | log.Printf("Shutting down Caché...\n") 261 | 262 | cmd := "ccontrol" 263 | args := []string{"stop", inst, "quietly"} 264 | 265 | if err := exec.Command(cmd, args...).Run(); err != nil { 266 | log.Printf("Error & possible causes:\n") 267 | log.Printf("-wrong Caché instance name") 268 | log.Printf("-Caché up in single user mode (there was trouble @startup)") 269 | log.Printf("ERR: %s", err) 270 | os.Exit(1) 271 | } 272 | 273 | // check that the shutdown was successful 274 | if err := checkCmdOutcome("down", inst, false); err != nil { 275 | log.Printf("Error: Caché was not shutdown successfully.\n") 276 | os.Exit(1) 277 | } 278 | 279 | return true, nil 280 | } 281 | 282 | // check that we are on a =>3.16 kernel 283 | // if not, set shmem size seg to shmem param 284 | // 285 | func checkKernelAndShmem(shmem int) error { 286 | var kVer float64 287 | var isLinuxType, isWindows bool 288 | 289 | // getting ready for clouds: Azure, AWS, GCP... 290 | switch runtime.GOOS { 291 | case "windows": 292 | if dbg { 293 | log.Printf("Checking kernel: it's Windows") 294 | } 295 | isWindows = true 296 | 297 | case "freebsd": 298 | if dbg { 299 | log.Printf("Checking kernel: it's freebsd") 300 | } 301 | isLinuxType = true 302 | 303 | case "linux": 304 | if dbg { 305 | log.Printf("Checking kernel: it's linux") 306 | } 307 | isLinuxType = true 308 | 309 | } 310 | 311 | if isLinuxType == true { 312 | 313 | // retrieve the kernel version 314 | ver, err := getKernelVersion() 315 | if err != nil { 316 | log.Printf("Error in checking kernel version") 317 | log.Printf("err: %s", err) 318 | os.Exit(1) 319 | } else { 320 | kVer = ver 321 | 322 | // if pre-3.16 we need to dynamically tune shmem 323 | if kVer < k316 { 324 | log.Printf("kernel version less than 3.16, auto-tuning it") 325 | 326 | // attempting to tune shmmax 327 | if _, err := setSharedMemSeg(shmem); err != nil { 328 | log.Printf("\nError setting shared memory: %s\n", err) 329 | os.Exit(1) 330 | } 331 | } else { 332 | //log.Printf("kernel version => 3.16; nothing to do.") 333 | } 334 | } 335 | 336 | } else if isWindows == true { 337 | log.Printf("Error: un-implemented") 338 | } 339 | 340 | return nil 341 | } 342 | 343 | // get the kernel version so that we know if we must tune it 344 | // 345 | func getKernelVersion() (float64, error) { 346 | var kVer float64 347 | cmd := "uname" 348 | args := []string{"-r"} 349 | 350 | c := exec.Command(cmd, args...) 351 | 352 | // organise to read the response form the bufferd IO 353 | var out bytes.Buffer 354 | c.Stdout = &out 355 | 356 | // and its execution 357 | if err := c.Run(); err != nil { 358 | log.Printf("Error & possible causes:\n") 359 | log.Printf("-insufficient privileges to run the uname command to find out the kernel version\n") 360 | log.Printf("-missing uname command in container\n") 361 | log.Printf("ERR: %s", err) 362 | os.Exit(1) 363 | } else { 364 | resp := out.String() 365 | if dbg { 366 | log.Printf("resp: %s\n", resp) 367 | } 368 | 369 | // Parsing the string from 'uname -r' 370 | // examples: 371 | // 3.8.0-19-generic Bodhi Linux on Ubuntu 372 | // 3.10.0-123.20.1.el7.x86_64 SLES12 373 | // 3.10.0-123.el7.x86_64 RHEL7 374 | // 3.16.6-2-desktop OpenSUSE 375 | // 3.10.0-123.20.1.el7.x86_64 CentOS 376 | // 3.16.0-34-generic Ubuntu 377 | // 378 | // breaking up the linux kernel version string into its constituencies and 379 | // reforming the string with only the first 2 version numbers of 380 | // major.minor so that ParseFloat can accept it 381 | // 382 | // kvVls is a []string 383 | kvVls := strings.Split(resp, ".") 384 | var shortVer string = "" 385 | version := make([]string, 2) 386 | 387 | // extract exactly the first 2 values we need from the Split func 388 | for k, kvPiece := range kvVls { 389 | if dbg { 390 | log.Printf("k-%d) kvPiece=%s", k, kvPiece) 391 | } 392 | 393 | if k < 2 { 394 | version[k] = kvPiece // kvVls[k] 395 | } 396 | } 397 | 398 | if dbg { 399 | log.Printf("[]version = %q", version) 400 | } 401 | 402 | // join the strings (which are in the slice) 403 | shortVer = strings.Join(version, ".") 404 | kVer, err = strconv.ParseFloat(shortVer, 64) 405 | if err != nil { 406 | log.Printf("Error converting to float: %s\n", err) 407 | os.Exit(1) 408 | } 409 | 410 | if dbg { 411 | log.Printf("kVer: %f; %s", kVer, shortVer) 412 | } 413 | } 414 | 415 | return kVer, nil 416 | } 417 | 418 | // start eXtra service(s) 419 | // launched as a goroutine so that bash or program does not hold our PID1 listening for SIGTERM 420 | // 421 | func startExtraService(exeCmd string, exeOK chan bool) { 422 | log.Printf("Starting eXtra service '%s' \n", exeCmd) 423 | 424 | cmdName := exeCmd 425 | 426 | // if user need to pass param to its shell it's preferable that they set environment variables 427 | // via the -e VAR1=VAL syntax when launching a container 428 | c := exec.Command(cmdName) 429 | 430 | // preparing for stdout/stderr msg 431 | var out bytes.Buffer 432 | c.Stdout = &out 433 | 434 | // with Start() there is no chance to monitor the return value... 435 | if err := c.Start(); err != nil { 436 | errMsg := out.String() 437 | log.Printf("Error in starting eXtra service: '%s'; Err: %s; %s\n", exeCmd, errMsg, err) 438 | //log.Printf("Err: %s; %s", errMsg, err) 439 | 440 | exeOK <- false 441 | 442 | } else { 443 | 444 | if dbg { 445 | log.Printf("exeOK == true") 446 | } 447 | 448 | // the call was OK 449 | // a user is advised to log error messages to the container logs 450 | exeOK <- true 451 | } 452 | } 453 | 454 | // Stopping eXtra service(s) 455 | // 456 | func stopExtraService(xstop string) (bool, error) { 457 | log.Printf("Shutting down eXtra service '%s'...\n", xstop) 458 | 459 | c := exec.Command(xstop) 460 | 461 | // preapring for stdout/stderr msg 462 | var out bytes.Buffer 463 | c.Stdout = &out 464 | 465 | if err := c.Run(); err != nil { 466 | errMsg := out.String() 467 | log.Printf("Error in stopping service(s) %s:", xstop) 468 | log.Printf("Err: %s; %s", errMsg, err) 469 | os.Exit(1) 470 | } 471 | 472 | return true, nil 473 | } 474 | 475 | // Caché container main 476 | // 477 | func main() { 478 | 479 | // flag handling 480 | pFinst := flag.String("i", "CACHE", "The Cachè instance name to start/stop") 481 | pFnmsp := flag.String("n", "", "The Caché application Namespace") 482 | pFrou := flag.String("r", "", "The Caché Routine name to start the app") 483 | pFstop := flag.Bool("cstop", true, "Allows container to avoid (false) Caché shutdown in case of throw-away containers") 484 | pFstart := flag.Bool("cstart", true, "Allows container to come up without (false) starting Caché or initialising shmem") 485 | pFnostu := flag.Bool("nostu", false, "Allows cstart to run with the nostu option for maintenance, single user access mode.") 486 | pFshmem := flag.Int("shmem", 512, "Shared Mem segment max size in MB; default value=512MB enough to install and play") 487 | pFlog := flag.Bool("cconsole", false, "Allows to show cconsole.log in current output.") 488 | pFail := flag.Bool("fail-if-troubled", false, "Container will fail if Caché does not start correctly") 489 | 490 | // user option to start other services he might need (sshd, whatever...) 491 | pFexeStart := flag.String("xstart", "", "Allows startup eXecution of other services or processes via a single ") 492 | pFexeStop := flag.String("xstop", "", "Allows stop eXecution of other services or processes via a single ") 493 | 494 | pVersion := flag.Bool("version", false, "prints version") 495 | 496 | flag.Parse() 497 | 498 | inst := *pFinst 499 | nmsp := *pFnmsp 500 | rou := *pFrou 501 | cstop := *pFstop 502 | cstart := *pFstart 503 | nostu := *pFnostu 504 | shmem := *pFshmem 505 | cclog := *pFlog 506 | fail := *pFail 507 | exeStart := *pFexeStart 508 | exeStop := *pFexeStop 509 | ver := *pVersion 510 | 511 | if dbg { 512 | log.Printf("flag instance: %s\n", inst) 513 | log.Printf("flag namesapce: %s\n", nmsp) 514 | log.Printf("flag routine: %s\n", rou) 515 | log.Printf("flag cstop: %t\n", cstop) 516 | log.Printf("flag cstart: %t\n", cstart) 517 | log.Printf("flag nostu: %t\n", nostu) 518 | log.Printf("flag shmem: %d\n", shmem) 519 | log.Printf("flag cconsole: %t\n", cclog) 520 | log.Printf("flag fail-if-troubled: %t\n", fail) 521 | log.Printf("flag xstart: %s\n", exeStart) 522 | log.Printf("flag xstop: %s\n", exeStop) 523 | log.Printf("flag v: %t\n", ver) 524 | 525 | log.Printf("command supplied: %q\n", flag.Args()) 526 | log.Printf("--\n") 527 | } 528 | 529 | // 0-- 530 | // version__________________________________________________________ 531 | if ver == true { 532 | fmt.Printf("ccontainermain Version %s\n", version) 533 | os.Exit(0) 534 | } 535 | 536 | // 1-- 537 | // starting up Caché services_______________________________________ 538 | // 539 | if cstart == true { 540 | 541 | // 1.1-- 542 | // check linux kernel version and if necessary tune shmmax seg 543 | if err := checkKernelAndShmem(shmem); err != nil { 544 | log.Printf("Error in checking kernel version") 545 | log.Printf("ERR: %s", err) 546 | os.Exit(1) 547 | } 548 | 549 | // 1.2-- 550 | // starting Caché 551 | _, err := startCaché(inst, nostu, cclog, fail) 552 | if err != nil { 553 | log.Printf("\nError starting up Caché: %s\n", err) 554 | os.Exit(1) 555 | } else { 556 | log.Printf("Caché is up.\n") 557 | } 558 | 559 | // 1.3-- 560 | // starting Caché app if we were told to 561 | if rou != "" && nmsp != "" { 562 | _, err = startApp(inst, nmsp, rou) 563 | if err != nil { 564 | log.Printf("\nError starting up the app %s in namespace %s; Err: %s\n", rou, nmsp, err) 565 | os.Exit(1) 566 | } else { 567 | log.Printf("App is up.\n") 568 | } 569 | } 570 | 571 | } else { 572 | // no point trying to bring it down if it was never started 573 | cstop = false 574 | } 575 | 576 | // 2-- 577 | // allow other services to start 578 | // 579 | if exeStart != "" { 580 | var exeOK bool 581 | 582 | // unbuffered blocking channel 583 | chExeCheck := make(chan bool) 584 | go startExtraService(exeStart, chExeCheck) 585 | 586 | exeOK = <-chExeCheck 587 | if exeOK != true { 588 | log.Printf("Error in Starting eXtra service '%s'", exeStart) 589 | } else { 590 | log.Printf("eXtra service is up.") 591 | } 592 | } 593 | 594 | // 3-- 595 | // signal trapping 596 | // buffered un-blocking channel 597 | // 598 | // we must use a buffered channel or risk missing the signal if we're not 599 | // ready to receive when the signal is sent. 600 | // 601 | cSig := make(chan os.Signal, 1) 602 | 603 | // checking for the most common interrupt signals 604 | signal.Notify(cSig, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT, syscall.SIGHUP) 605 | 606 | // Block until a signal is received_____________ 607 | sig := <-cSig 608 | log.Printf("Signal trapped; sig. %s; %d\n", sig, sig) 609 | 610 | // if SIG*... received then run shutdown 611 | 612 | // 4-- 613 | // Bring Caché down cleanly 614 | // 615 | if cstop == true { 616 | _, err := shutdownCaché(inst) 617 | if err != nil { 618 | log.Printf("\nError shutting down Caché: %s\n", err) 619 | os.Exit(1) 620 | } else { 621 | log.Printf("Caché is down.\n") 622 | } 623 | } 624 | 625 | // 5-- 626 | // bring down the extra service(s). 627 | // 628 | if exeStop != "" { 629 | _, err := stopExtraService(exeStop) 630 | if err != nil { 631 | log.Printf("\nError shutting down eXtra service: %s; err: %s\n", exeStop, err) 632 | os.Exit(1) 633 | } else { 634 | log.Printf("eXtra service down.\n") 635 | } 636 | } 637 | } 638 | --------------------------------------------------------------------------------