├── .travis.yml ├── distrib └── linux │ └── ccontainermain ├── dockerfile └── Caché+SSH │ ├── ccontrol-wrapper.sh │ └── Dockerfile ├── Makefile ├── .github └── workflows │ └── go.yml ├── 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/zrml/ccontainermain/HEAD/distrib/linux/ccontainermain -------------------------------------------------------------------------------- /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 | df / | grep -q overlay 6 | filesystemIsOverlay=$? 7 | 8 | if [ "${1,,}" == "start" ] && [ $filesystemIsOverlay -eq 0 ]; then 9 | find / -name CACHE.DAT -exec touch {} \; 10 | fi 11 | 12 | /usr/local/etc/cachesys/ccontrol $@ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for a project 2 | 3 | BINARY=ccontainermain 4 | 5 | OS=linux 6 | ARCH=amd64 7 | LDFLAGS= 8 | 9 | .PHONY: all ccontainermain get-deps clean 10 | 11 | all: ccontainermain 12 | 13 | ccontainermain: get-deps distrib/${OS}/ccontainermain 14 | 15 | distrib/${OS}/ccontainermain: ccontainermain.go 16 | env GOOS=${OS} GOARCH=${ARCH} go build ${LDFLAGS} -o distrib/${OS}/${BINARY} ccontainermain.go 17 | 18 | get-deps: 19 | go get github.com/hpcloud/tail 20 | 21 | clean: 22 | -rm distrib/${OS}/ccontainermain 23 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /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 | * -xprestart for eXecuting anything before Caché is started; -xprestart=/myapp/runMyExtraServive.sh 42 | * -xstart for eXecuting anything after Caché is started; -xstart=/myapp/runMyExtraServive.sh 43 | * -xstop for eXecuting anything after the Caché shutdown; -xstop=/bringAllMyProcsDown.sh 44 | * -cstart it's a boolean defaulted to true; It gives us the option to start a container without starting Caché; -cstart=true 45 | * -cstop it's a boolean defaulted to true; it gives the option to skip the Caché shutdown; -cstop=false 46 | * -nostu it's a boolean defaulted to false; it allows DB single user startup for maintenance mode 47 | * -cconsole it's a boolean defaulted to false; it shows Caché cconsole.log output in the container logs output 48 | * -version shows ccontainer version 49 | 50 | The above flags can also be retrieved via 51 | 52 | $ ./ccontainermain -help 53 | 54 | 55 | For more information on the Caché `ccontrol` related options please see: 56 | [InteSystems documentation] (http://docs.intersystems.com/cache201511/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_using_instance#GSA_using_instance_control) 57 | and for the rest see 58 | [the Docker documentation] (https://docs.docker.com/) 59 | 60 | Please note that I've left in a debug (dbg) constant that you can use to get extra debugging information throughout the program. 61 | For your convenience a linux executable has been provided so that you don't have to install GO and compile the code. 62 | If you have GO installed simply 63 | 64 | $ go build ccontainermain.go 65 | 66 | 67 | 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. 68 | 69 | HTH 70 | 71 | 72 | ## TODO 73 | * investigate SIGCHLD for dying processes and clean up PID table 74 | * Windows/Azure support and testing 75 | 76 | -------------------------------------------------------------------------------- /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 52 | // in pre-3.16 SHMALL is 8192MB so one single segment (shmmax) cannot be larger than that 53 | if shmmaxVal > pre316MaxShmall { 54 | log.Printf("Warning: std pre-3.16 linux kernel has only 8192MB of total shared memory (shmall); Setting shmmax to this limit (8GB)") 55 | shmmaxVal = pre316MaxShmall 56 | } 57 | 58 | shmmaxByteVal := (shmmaxVal * 1024 * 1024) 59 | if dbg { 60 | log.Printf("shmmaxByteVal: %d", shmmaxByteVal) 61 | } 62 | 63 | // concatenating the cmd string 64 | kstr := []string{"kernel.shmmax=", strconv.Itoa(shmmaxByteVal)} 65 | kernelParamShmmax := strings.Join(kstr, "") 66 | if dbg { 67 | log.Printf("kernelParamShmmax: %s", kernelParamShmmax) 68 | } 69 | 70 | // the OS command to set the new shmmax kernel param 71 | cmd := "sysctl" 72 | args := []string{"-w", kernelParamShmmax} 73 | 74 | // and its execution 75 | if err := exec.Command(cmd, args...).Run(); err != nil { 76 | log.Printf("Error & possible causes:\n") 77 | log.Printf("-insufficient privileges to run sysctl; you must be on < 3.16 kernel\n") 78 | log.Printf("-container running without --privileged flag (needed for setting shared mem)\n") 79 | log.Printf("ERR: %s", err) 80 | os.Exit(1) 81 | } 82 | 83 | return true, nil 84 | } 85 | 86 | // returns instalation folder for given instance 87 | func getInstanceFolder(inst string) string { 88 | var folder string 89 | cmd := "ccontrol" 90 | args := []string{"qlist", inst} 91 | 92 | // Output runs the command and returns its standard output 93 | if out, err := exec.Command(cmd, args...).Output(); err != nil { 94 | log.Printf("Error while getting Cachéinstallation folders\n") 95 | log.Printf("ERR: %s.", err) 96 | os.Exit(1) 97 | 98 | } else { 99 | // ccontrol qlist string examples: 100 | // C151^/usr/cachesys^2015.1.0.429.0^running, since Mon Jun 8 12:00:30 2015^cache.cpf^1972^57772^62972^warn^ 101 | // 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^ 102 | // C151^/usr/cachesys^2015.1.0.429.0^down, last used Mon Jun 8 16:40:07 2015^cache.cpf^1972^57772^62972^^ 103 | qlistStr := string(out) 104 | if qlistStr == "" { 105 | log.Printf("Error: Cannot continue as qlistStr from 'ccontrol qlist ' is empty") 106 | os.Exit(1) 107 | } 108 | 109 | // parsing the returned string; they're all []string... 110 | folder = strings.SplitN(qlistStr, "^", 4)[1] 111 | } 112 | 113 | return folder 114 | } 115 | 116 | // shows all new lines in cconsole.log 117 | func tailCConsoleLog(inst string) { 118 | folder := getInstanceFolder(inst) 119 | endLocation := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} 120 | if t, err := tail.TailFile(folder+"/mgr/cconsole.log", tail.Config{Follow: true, Location: &endLocation}); err != nil { 121 | log.Printf("Error while getting content for cconsole.log\n") 122 | log.Printf("ERR: %s.\n", err) 123 | } else { 124 | for line := range t.Lines { 125 | fmt.Println(line.Text) 126 | } 127 | } 128 | } 129 | 130 | // starting Caché 131 | // 132 | func startCaché(inst string, nostu bool, cclog bool, fail bool) (bool, error) { 133 | log.Printf("Starting Caché...\n") 134 | 135 | // building the start string 136 | cmd := "ccontrol" 137 | args := []string{"start"} 138 | args = append(args, inst) 139 | if nostu == true { 140 | args = append(args, "nostu") 141 | } 142 | args = append(args, "quietly") 143 | 144 | if dbg { 145 | log.Printf("Caché start cmd: %s %q", cmd, args) 146 | } 147 | 148 | if cclog { 149 | go tailCConsoleLog(inst) 150 | } 151 | 152 | c := exec.Command(cmd, args...) 153 | 154 | // preparing for stdout/stderr msg 155 | var out bytes.Buffer 156 | c.Stdout = &out 157 | 158 | // run it 159 | if err := c.Run(); err != nil { 160 | errMsg := out.String() 161 | log.Printf("Error & possible causes:\n") 162 | log.Printf("-Caché was not installed successfully") 163 | log.Printf("-wrong Caché instance name") 164 | log.Printf("-missing privileges to start/stop Caché; proc not in Caché group.") 165 | log.Printf("ERR: %s; %s", err, errMsg) 166 | os.Exit(1) 167 | } 168 | 169 | // check that the start-up was successful 170 | if err := checkCmdOutcome("up", inst, fail); err != nil { 171 | log.Printf("Error: Caché was not brought up successfully.\n") 172 | os.Exit(1) 173 | } 174 | 175 | return true, nil 176 | } 177 | 178 | // check if cstart or cstop were successfull 179 | // what = what we are checking: 180 | // "up" checks for successful Caché start-up 181 | // "down" checks for successful Caché shutdown 182 | // 183 | func checkCmdOutcome(what string, inst string, fail bool) error { 184 | cmd := "ccontrol" 185 | args := []string{"qlist", inst} 186 | 187 | // Output runs the command and returns its standard output 188 | if out, err := exec.Command(cmd, args...).Output(); err != nil { 189 | log.Printf("Error while verifying Caché '%s' status\n", what) 190 | log.Printf("ERR: %s.", err) 191 | os.Exit(1) 192 | 193 | } else { 194 | // ccontrol qlist string examples: 195 | // C151^/usr/cachesys^2015.1.0.429.0^running, since Mon Jun 8 12:00:30 2015^cache.cpf^1972^57772^62972^warn^ 196 | // 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^ 197 | // C151^/usr/cachesys^2015.1.0.429.0^down, last used Mon Jun 8 16:40:07 2015^cache.cpf^1972^57772^62972^^ 198 | qlistStr := string(out) 199 | if qlistStr == "" { 200 | log.Printf("Error: Cannot continue as qlistStr from 'ccontrol qlist ' is empty") 201 | os.Exit(1) 202 | } 203 | 204 | // parsing the returned string; they're all []string... 205 | upDownStr := strings.SplitN(qlistStr, "^", 4) 206 | 207 | // we are only interested in the 1st word: "running", "down" etc. 208 | CachéStatus := strings.SplitN(upDownStr[3], ",", 2) 209 | cstatus := CachéStatus[0] 210 | 211 | if cstatus == "running" { 212 | log.Printf("Caché started successfully\n") 213 | 214 | } else if cstatus == "down" { 215 | log.Printf("Caché stopped successfully\n") 216 | 217 | } else if cstatus == "sign-on inhibited" { 218 | log.Printf("Something is preventing Caché from starting in multi-user mode,\n") 219 | log.Printf("You might want to start the container with the flag -cstart=false to fix it.\n") 220 | 221 | if fail == true { 222 | return errors.New("sign-on inhibited") 223 | } 224 | } else { 225 | log.Printf("Un-recognized Caché status while trying to verify its '%s' status\n", what) 226 | log.Printf("-qlist string = %s.\n", qlistStr) 227 | } 228 | } 229 | 230 | return nil 231 | } 232 | 233 | // starting Caché app by calling a routine or class method 234 | // 235 | func startApp(inst string, nmsp string, rou string) (bool, error) { 236 | log.Printf("Starting app '%s' in '%s'...\n", rou, nmsp) 237 | 238 | cmd := "ccontrol" 239 | args := []string{"session", inst, "-U", nmsp, rou} 240 | c := exec.Command(cmd, args...) 241 | 242 | if dbg { 243 | log.Printf("cmd & args: %s, %q", cmd, args) 244 | } 245 | 246 | // preapring for stdout/stderr msg 247 | var out bytes.Buffer 248 | c.Stdout = &out 249 | 250 | if err := c.Run(); err != nil { 251 | errMsg := out.String() 252 | log.Printf("Error in launching routine %s in namespace %s:", rou, nmsp) 253 | log.Printf("Err: %s; %s", err, errMsg) 254 | os.Exit(1) 255 | } 256 | 257 | return true, nil 258 | } 259 | 260 | // Stopping Caché 261 | // 262 | func shutdownCaché(inst string) (bool, error) { 263 | log.Printf("Shutting down Caché...\n") 264 | 265 | cmd := "ccontrol" 266 | args := []string{"stop", inst, "quietly"} 267 | 268 | if err := exec.Command(cmd, args...).Run(); err != nil { 269 | log.Printf("Error & possible causes:\n") 270 | log.Printf("-wrong Caché instance name") 271 | log.Printf("-Caché up in single user mode (there was trouble @startup)") 272 | log.Printf("ERR: %s", err) 273 | os.Exit(1) 274 | } 275 | 276 | // check that the shutdown was successful 277 | if err := checkCmdOutcome("down", inst, false); err != nil { 278 | log.Printf("Error: Caché was not shutdown successfully.\n") 279 | os.Exit(1) 280 | } 281 | 282 | return true, nil 283 | } 284 | 285 | // check that we are on a =>3.16 kernel 286 | // if not, set shmem size seg to shmem param 287 | // 288 | func checkKernelAndShmem(shmem int) error { 289 | var kVer float64 290 | var isLinuxType, isWindows bool 291 | 292 | // getting ready for clouds: Azure, AWS, GCP... 293 | switch runtime.GOOS { 294 | case "windows": 295 | if dbg { 296 | log.Printf("Checking kernel: it's Windows") 297 | } 298 | isWindows = true 299 | 300 | case "freebsd": 301 | if dbg { 302 | log.Printf("Checking kernel: it's freebsd") 303 | } 304 | isLinuxType = true 305 | 306 | case "linux": 307 | if dbg { 308 | log.Printf("Checking kernel: it's linux") 309 | } 310 | isLinuxType = true 311 | 312 | } 313 | 314 | if isLinuxType == true { 315 | 316 | // retrieve the kernel version 317 | ver, err := getKernelVersion() 318 | if err != nil { 319 | log.Printf("Error in checking kernel version") 320 | log.Printf("err: %s", err) 321 | os.Exit(1) 322 | } else { 323 | kVer = ver 324 | 325 | // if pre-3.16 we need to dynamically tune shmem 326 | if kVer < k316 { 327 | log.Printf("kernel version less than 3.16, auto-tuning it") 328 | 329 | // attempting to tune shmmax 330 | if _, err := setSharedMemSeg(shmem); err != nil { 331 | log.Printf("\nError setting shared memory: %s\n", err) 332 | os.Exit(1) 333 | } 334 | } else { 335 | //log.Printf("kernel version => 3.16; nothing to do.") 336 | } 337 | } 338 | 339 | } else if isWindows == true { 340 | log.Printf("Error: un-implemented") 341 | } 342 | 343 | return nil 344 | } 345 | 346 | // get the kernel version so that we know if we must tune it 347 | // 348 | func getKernelVersion() (float64, error) { 349 | var kVer float64 350 | cmd := "uname" 351 | args := []string{"-r"} 352 | 353 | c := exec.Command(cmd, args...) 354 | 355 | // organise to read the response form the bufferd IO 356 | var out bytes.Buffer 357 | c.Stdout = &out 358 | 359 | // and its execution 360 | if err := c.Run(); err != nil { 361 | log.Printf("Error & possible causes:\n") 362 | log.Printf("-insufficient privileges to run the uname command to find out the kernel version\n") 363 | log.Printf("-missing uname command in container\n") 364 | log.Printf("ERR: %s", err) 365 | os.Exit(1) 366 | } else { 367 | resp := out.String() 368 | if dbg { 369 | log.Printf("resp: %s\n", resp) 370 | } 371 | 372 | // Parsing the string from 'uname -r' 373 | // examples: 374 | // 3.8.0-19-generic Bodhi Linux on Ubuntu 375 | // 3.10.0-123.20.1.el7.x86_64 SLES12 376 | // 3.10.0-123.el7.x86_64 RHEL7 377 | // 3.16.6-2-desktop OpenSUSE 378 | // 3.10.0-123.20.1.el7.x86_64 CentOS 379 | // 3.16.0-34-generic Ubuntu 380 | // 381 | // breaking up the linux kernel version string into its constituencies and 382 | // reforming the string with only the first 2 version numbers of 383 | // major.minor so that ParseFloat can accept it 384 | // 385 | // kvVls is a []string 386 | kvVls := strings.Split(resp, ".") 387 | var shortVer string = "" 388 | version := make([]string, 2) 389 | 390 | // extract exactly the first 2 values we need from the Split func 391 | for k, kvPiece := range kvVls { 392 | if dbg { 393 | log.Printf("k-%d) kvPiece=%s", k, kvPiece) 394 | } 395 | 396 | if k < 2 { 397 | version[k] = kvPiece // kvVls[k] 398 | } 399 | } 400 | 401 | if dbg { 402 | log.Printf("[]version = %q", version) 403 | } 404 | 405 | // join the strings (which are in the slice) 406 | shortVer = strings.Join(version, ".") 407 | kVer, err = strconv.ParseFloat(shortVer, 64) 408 | if err != nil { 409 | log.Printf("Error converting to float: %s\n", err) 410 | os.Exit(1) 411 | } 412 | 413 | if dbg { 414 | log.Printf("kVer: %f; %s", kVer, shortVer) 415 | } 416 | } 417 | 418 | return kVer, nil 419 | } 420 | 421 | // start eXtra service(s) 422 | // launched as a goroutine so that bash or program does not hold our PID1 listening for SIGTERM 423 | // 424 | func startExtraService(exeCmd string, exeOK chan bool) { 425 | log.Printf("Starting eXtra service '%s' \n", exeCmd) 426 | 427 | cmdName := exeCmd 428 | 429 | // if user need to pass param to its shell it's preferable that they set environment variables 430 | // via the -e VAR1=VAL syntax when launching a container 431 | c := exec.Command(cmdName) 432 | 433 | // preparing for stdout/stderr msg 434 | c.Stdout = os.Stdout 435 | 436 | var stderr bytes.Buffer 437 | c.Stderr = &stderr 438 | 439 | // with Run() there is no chance to monitor the return value... 440 | if err := c.Run(); err != nil { 441 | errMsg := stderr.String() 442 | log.Printf("Error in starting eXtra service: '%s'; Err: %s; %s\n", exeCmd, errMsg, err) 443 | //log.Printf("Err: %s; %s", errMsg, err) 444 | 445 | exeOK <- false 446 | 447 | } else { 448 | 449 | if dbg { 450 | log.Printf("exeOK == true") 451 | } 452 | 453 | // the call was OK 454 | // a user is advised to log error messages to the container logs 455 | exeOK <- true 456 | } 457 | } 458 | 459 | // Stopping eXtra service(s) 460 | // 461 | func stopExtraService(xstop string) (bool, error) { 462 | log.Printf("Shutting down eXtra service '%s'...\n", xstop) 463 | 464 | c := exec.Command(xstop) 465 | 466 | // preapring for stdout/stderr msg 467 | var out bytes.Buffer 468 | c.Stdout = &out 469 | 470 | if err := c.Run(); err != nil { 471 | errMsg := out.String() 472 | log.Printf("Error in stopping service(s) %s:", xstop) 473 | log.Printf("Err: %s; %s", errMsg, err) 474 | os.Exit(1) 475 | } 476 | 477 | return true, nil 478 | } 479 | 480 | // Caché container main 481 | // 482 | func main() { 483 | 484 | // flag handling 485 | pFinst := flag.String("i", "CACHE", "The Cachè instance name to start/stop") 486 | pFnmsp := flag.String("n", "", "The Caché application Namespace") 487 | pFrou := flag.String("r", "", "The Caché Routine name to start the app") 488 | pFstop := flag.Bool("cstop", true, "Allows container to avoid (false) Caché shutdown in case of throw-away containers") 489 | pFstart := flag.Bool("cstart", true, "Allows container to come up without (false) starting Caché or initialising shmem") 490 | pFnostu := flag.Bool("nostu", false, "Allows cstart to run with the nostu option for maintenance, single user access mode.") 491 | pFshmem := flag.Int("shmem", 512, "Shared Mem segment max size in MB; default value=512MB enough to install and play") 492 | pFlog := flag.Bool("cconsole", false, "Allows to show cconsole.log in current output.") 493 | pFail := flag.Bool("fail-if-troubled", false, "Container will fail if Caché does not start correctly") 494 | 495 | // user option to start other services he might need (sshd, whatever...) 496 | pFexePreStart := flag.String("xprestart", "", "Allows startup eXecution of other services or processes via a single , called before Caché starts") 497 | pFexeStart := flag.String("xstart", "", "Allows startup eXecution of other services or processes via a single , called after Caché starts") 498 | pFexeStop := flag.String("xstop", "", "Allows stop eXecution of other services or processes via a single ") 499 | 500 | pVersion := flag.Bool("version", false, "prints version") 501 | 502 | flag.Parse() 503 | 504 | inst := *pFinst 505 | nmsp := *pFnmsp 506 | rou := *pFrou 507 | cstop := *pFstop 508 | cstart := *pFstart 509 | nostu := *pFnostu 510 | shmem := *pFshmem 511 | cclog := *pFlog 512 | fail := *pFail 513 | exePreStart := *pFexePreStart 514 | exeStart := *pFexeStart 515 | exeStop := *pFexeStop 516 | ver := *pVersion 517 | 518 | if dbg { 519 | log.Printf("flag instance: %s\n", inst) 520 | log.Printf("flag namesapce: %s\n", nmsp) 521 | log.Printf("flag routine: %s\n", rou) 522 | log.Printf("flag cstop: %t\n", cstop) 523 | log.Printf("flag cstart: %t\n", cstart) 524 | log.Printf("flag nostu: %t\n", nostu) 525 | log.Printf("flag shmem: %d\n", shmem) 526 | log.Printf("flag cconsole: %t\n", cclog) 527 | log.Printf("flag fail-if-troubled: %t\n", fail) 528 | log.Printf("flag xprestart: %s\n", exePreStart) 529 | log.Printf("flag xstart: %s\n", exeStart) 530 | log.Printf("flag xstop: %s\n", exeStop) 531 | log.Printf("flag v: %t\n", ver) 532 | 533 | log.Printf("command supplied: %q\n", flag.Args()) 534 | log.Printf("--\n") 535 | } 536 | 537 | // 0-- 538 | // version__________________________________________________________ 539 | if ver == true { 540 | fmt.Printf("ccontainermain Version %s\n", version) 541 | os.Exit(0) 542 | } 543 | 544 | // allow other services to run before Cache starts 545 | // 546 | if exePreStart != "" { 547 | var exeOK bool 548 | 549 | chExeCheck := make(chan bool) 550 | go startExtraService(exePreStart, chExeCheck) 551 | 552 | exeOK = <-chExeCheck 553 | 554 | if exeOK != true { 555 | log.Printf("Error starting eXtra pre-start service '%s'", exeStart) 556 | } else { 557 | log.Printf("eXtra pre-start service is up.") 558 | } 559 | } 560 | 561 | // 1-- 562 | // starting up Caché services_______________________________________ 563 | // 564 | if cstart == true { 565 | 566 | // 1.1-- 567 | // check linux kernel version and if necessary tune shmmax seg 568 | if err := checkKernelAndShmem(shmem); err != nil { 569 | log.Printf("Error in checking kernel version") 570 | log.Printf("ERR: %s", err) 571 | os.Exit(1) 572 | } 573 | 574 | // 1.2-- 575 | // starting Caché 576 | _, err := startCaché(inst, nostu, cclog, fail) 577 | if err != nil { 578 | log.Printf("\nError starting up Caché: %s\n", err) 579 | os.Exit(1) 580 | } else { 581 | log.Printf("Caché is up.\n") 582 | } 583 | 584 | // 1.3-- 585 | // starting Caché app if we were told to 586 | if rou != "" && nmsp != "" { 587 | _, err = startApp(inst, nmsp, rou) 588 | if err != nil { 589 | log.Printf("\nError starting up the app %s in namespace %s; Err: %s\n", rou, nmsp, err) 590 | os.Exit(1) 591 | } else { 592 | log.Printf("App is up.\n") 593 | } 594 | } 595 | 596 | } else { 597 | // no point trying to bring it down if it was never started 598 | cstop = false 599 | } 600 | 601 | // 2-- 602 | // allow other services to start 603 | // 604 | if exeStart != "" { 605 | var exeOK bool 606 | 607 | // unbuffered blocking channel 608 | chExeCheck := make(chan bool) 609 | go startExtraService(exeStart, chExeCheck) 610 | 611 | exeOK = <-chExeCheck 612 | if exeOK != true { 613 | log.Printf("Error in Starting eXtra service '%s'", exeStart) 614 | } else { 615 | log.Printf("eXtra service is up.") 616 | } 617 | } 618 | 619 | // 3-- 620 | // signal trapping 621 | // buffered un-blocking channel 622 | // 623 | // we must use a buffered channel or risk missing the signal if we're not 624 | // ready to receive when the signal is sent. 625 | // 626 | cSig := make(chan os.Signal, 1) 627 | 628 | // checking for the most common interrupt signals 629 | signal.Notify(cSig, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT, syscall.SIGHUP) 630 | 631 | // Block until a signal is received_____________ 632 | sig := <-cSig 633 | log.Printf("Signal trapped; sig. %s; %d\n", sig, sig) 634 | 635 | // if SIG*... received then run shutdown 636 | 637 | // 4-- 638 | // Bring Caché down cleanly 639 | // 640 | if cstop == true { 641 | _, err := shutdownCaché(inst) 642 | if err != nil { 643 | log.Printf("\nError shutting down Caché: %s\n", err) 644 | os.Exit(1) 645 | } else { 646 | log.Printf("Caché is down.\n") 647 | } 648 | } 649 | 650 | // 5-- 651 | // bring down the extra service(s). 652 | // 653 | if exeStop != "" { 654 | _, err := stopExtraService(exeStop) 655 | if err != nil { 656 | log.Printf("\nError shutting down eXtra service: %s; err: %s\n", exeStop, err) 657 | os.Exit(1) 658 | } else { 659 | log.Printf("eXtra service down.\n") 660 | } 661 | } 662 | } 663 | --------------------------------------------------------------------------------