2018-10-05 19:12:03 +00:00
package rkt
import (
"bytes"
2018-11-13 01:09:27 +00:00
"context"
2018-10-17 00:14:21 +00:00
"encoding/json"
2018-10-05 19:12:03 +00:00
"fmt"
2018-10-17 00:14:21 +00:00
"io/ioutil"
"math/rand"
2018-10-05 19:12:03 +00:00
"net"
"os"
"os/exec"
"path/filepath"
"regexp"
2018-10-17 00:14:21 +00:00
"strconv"
2018-10-05 19:12:03 +00:00
"strings"
2019-01-07 22:47:49 +00:00
"sync"
2018-10-05 19:12:03 +00:00
"syscall"
"time"
2018-10-12 17:37:28 +00:00
appcschema "github.com/appc/spec/schema"
2018-10-17 00:14:21 +00:00
"github.com/hashicorp/consul-template/signals"
2018-12-19 23:32:43 +00:00
hclog "github.com/hashicorp/go-hclog"
version "github.com/hashicorp/go-version"
2018-10-05 19:12:03 +00:00
"github.com/hashicorp/nomad/client/config"
2018-11-30 11:18:39 +00:00
"github.com/hashicorp/nomad/client/taskenv"
2018-10-12 17:37:28 +00:00
"github.com/hashicorp/nomad/drivers/shared/eventer"
2018-12-07 01:54:14 +00:00
"github.com/hashicorp/nomad/drivers/shared/executor"
2019-01-14 21:33:42 +00:00
"github.com/hashicorp/nomad/helper"
drivers: restore port_map old json support
This ensures that `port_map` along with other block like attribute
declarations (e.g. ulimit, labels, etc) can handle various hcl and json
syntax that was supported in 0.8.
In 0.8.7, the following declarations are effectively equivalent:
```
// hcl block
port_map {
http = 80
https = 443
}
// hcl assignment
port_map = {
http = 80
https = 443
}
// json single element array of map (default in API response)
{"port_map": [{"http": 80, "https": 443}]}
// json array of individual maps (supported accidentally iiuc)
{"port_map: [{"http": 80}, {"https": 443}]}
```
We achieve compatbility by using `NewAttr("...", "list(map(string))",
false)` to be serialized to a `map[string]string` wrapper, instead of using
`BlockAttrs` declaration. The wrapper merges the list of maps
automatically, to ease driver development.
This approach is closer to how v0.8.7 implemented the fields [1][2], and
despite its verbosity, seems to perserve 0.8.7 behavior in hcl2.
This is only required for built-in types that have backward
compatibility constraints. External drivers should use `BlockAttrs`
instead, as they see fit.
[1] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L216
[2] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L698-L700
2019-02-13 17:55:48 +00:00
"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
2019-01-23 14:27:14 +00:00
"github.com/hashicorp/nomad/helper/pluginutils/loader"
2018-10-05 19:12:03 +00:00
"github.com/hashicorp/nomad/plugins/base"
"github.com/hashicorp/nomad/plugins/drivers"
"github.com/hashicorp/nomad/plugins/shared/hclspec"
2018-11-21 00:30:39 +00:00
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
2018-10-05 19:12:03 +00:00
)
const (
// pluginName is the name of the plugin
pluginName = "rkt"
// fingerprintPeriod is the interval at which the driver will send fingerprint responses
fingerprintPeriod = 30 * time . Second
// minRktVersion is the earliest supported version of rkt. rkt added support
// for CPU and memory isolators in 0.14.0. We cannot support an earlier
// version to maintain an uniform interface across all drivers
minRktVersion = "1.27.0"
// rktCmd is the command rkt is installed as.
rktCmd = "rkt"
2018-10-30 21:05:31 +00:00
// networkDeadline is how long to wait for container network
// information to become available.
networkDeadline = 1 * time . Minute
2019-01-17 02:52:31 +00:00
// taskHandleVersion is the version of task handle which this driver sets
// and understands how to decode driver state
taskHandleVersion = 1
2018-10-05 19:12:03 +00:00
)
2018-10-31 00:37:00 +00:00
var (
// PluginID is the rawexec plugin metadata registered in the plugin
// catalog.
PluginID = loader . PluginID {
Name : pluginName ,
PluginType : base . PluginTypeDriver ,
}
// PluginConfig is the rawexec factory function registered in the
// plugin catalog.
PluginConfig = & loader . InternalPluginConfig {
Config : map [ string ] interface { } { } ,
Factory : func ( l hclog . Logger ) interface { } { return NewRktDriver ( l ) } ,
}
)
// PluginLoader maps pre-0.9 client driver options to post-0.9 plugin options.
func PluginLoader ( opts map [ string ] string ) ( map [ string ] interface { } , error ) {
conf := map [ string ] interface { } { }
if v , err := strconv . ParseBool ( opts [ "driver.rkt.volumes.enabled" ] ) ; err == nil {
conf [ "volumes_enabled" ] = v
}
return conf , nil
}
2018-10-05 19:12:03 +00:00
var (
// pluginInfo is the response returned for the PluginInfo RPC
pluginInfo = & base . PluginInfoResponse {
2018-12-18 00:40:58 +00:00
Type : base . PluginTypeDriver ,
PluginApiVersions : [ ] string { drivers . ApiVersion010 } ,
PluginVersion : "0.1.0" ,
Name : pluginName ,
2018-10-05 19:12:03 +00:00
}
// configSpec is the hcl specification returned by the ConfigSchema RPC
configSpec = hclspec . NewObject ( map [ string ] * hclspec . Spec {
"volumes_enabled" : hclspec . NewDefault (
hclspec . NewAttr ( "volumes_enabled" , "bool" , false ) ,
hclspec . NewLiteral ( "true" ) ,
) ,
} )
// taskConfigSpec is the hcl specification for the driver config section of
2018-10-12 17:37:28 +00:00
// a taskConfig within a job. It is returned in the TaskConfigSchema RPC
2018-10-05 19:12:03 +00:00
taskConfigSpec = hclspec . NewObject ( map [ string ] * hclspec . Spec {
"image" : hclspec . NewAttr ( "image" , "string" , true ) ,
2018-10-12 17:37:28 +00:00
"command" : hclspec . NewAttr ( "command" , "string" , false ) ,
"args" : hclspec . NewAttr ( "args" , "list(string)" , false ) ,
2018-10-05 19:12:03 +00:00
"trust_prefix" : hclspec . NewAttr ( "trust_prefix" , "string" , false ) ,
"dns_servers" : hclspec . NewAttr ( "dns_servers" , "list(string)" , false ) ,
"dns_search_domains" : hclspec . NewAttr ( "dns_search_domains" , "list(string)" , false ) ,
"net" : hclspec . NewAttr ( "net" , "list(string)" , false ) ,
drivers: restore port_map old json support
This ensures that `port_map` along with other block like attribute
declarations (e.g. ulimit, labels, etc) can handle various hcl and json
syntax that was supported in 0.8.
In 0.8.7, the following declarations are effectively equivalent:
```
// hcl block
port_map {
http = 80
https = 443
}
// hcl assignment
port_map = {
http = 80
https = 443
}
// json single element array of map (default in API response)
{"port_map": [{"http": 80, "https": 443}]}
// json array of individual maps (supported accidentally iiuc)
{"port_map: [{"http": 80}, {"https": 443}]}
```
We achieve compatbility by using `NewAttr("...", "list(map(string))",
false)` to be serialized to a `map[string]string` wrapper, instead of using
`BlockAttrs` declaration. The wrapper merges the list of maps
automatically, to ease driver development.
This approach is closer to how v0.8.7 implemented the fields [1][2], and
despite its verbosity, seems to perserve 0.8.7 behavior in hcl2.
This is only required for built-in types that have backward
compatibility constraints. External drivers should use `BlockAttrs`
instead, as they see fit.
[1] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L216
[2] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L698-L700
2019-02-13 17:55:48 +00:00
"port_map" : hclspec . NewAttr ( "port_map" , "list(map(string))" , false ) ,
2018-10-05 19:12:03 +00:00
"volumes" : hclspec . NewAttr ( "volumes" , "list(string)" , false ) ,
"insecure_options" : hclspec . NewAttr ( "insecure_options" , "list(string)" , false ) ,
"no_overlay" : hclspec . NewAttr ( "no_overlay" , "bool" , false ) ,
"debug" : hclspec . NewAttr ( "debug" , "bool" , false ) ,
"group" : hclspec . NewAttr ( "group" , "string" , false ) ,
} )
// capabilities is returned by the Capabilities RPC and indicates what
// optional features this driver supports
capabilities = & drivers . Capabilities {
SendSignals : true ,
Exec : true ,
2019-01-04 21:11:25 +00:00
FSIsolation : drivers . FSIsolationImage ,
2018-10-05 19:12:03 +00:00
}
reRktVersion = regexp . MustCompile ( ` rkt [vV]ersion[:]? (\d[.\d]+) ` )
reAppcVersion = regexp . MustCompile ( ` appc [vV]ersion[:]? (\d[.\d]+) ` )
)
// Config is the client configuration for the driver
type Config struct {
// VolumesEnabled allows tasks to bind host paths (volumes) inside their
// container. Binding relative paths is always allowed and will be resolved
// relative to the allocation's directory.
VolumesEnabled bool ` codec:"volumes_enabled" `
}
2018-10-12 17:37:28 +00:00
// TaskConfig is the driver configuration of a taskConfig within a job
2018-10-05 19:12:03 +00:00
type TaskConfig struct {
drivers: restore port_map old json support
This ensures that `port_map` along with other block like attribute
declarations (e.g. ulimit, labels, etc) can handle various hcl and json
syntax that was supported in 0.8.
In 0.8.7, the following declarations are effectively equivalent:
```
// hcl block
port_map {
http = 80
https = 443
}
// hcl assignment
port_map = {
http = 80
https = 443
}
// json single element array of map (default in API response)
{"port_map": [{"http": 80, "https": 443}]}
// json array of individual maps (supported accidentally iiuc)
{"port_map: [{"http": 80}, {"https": 443}]}
```
We achieve compatbility by using `NewAttr("...", "list(map(string))",
false)` to be serialized to a `map[string]string` wrapper, instead of using
`BlockAttrs` declaration. The wrapper merges the list of maps
automatically, to ease driver development.
This approach is closer to how v0.8.7 implemented the fields [1][2], and
despite its verbosity, seems to perserve 0.8.7 behavior in hcl2.
This is only required for built-in types that have backward
compatibility constraints. External drivers should use `BlockAttrs`
instead, as they see fit.
[1] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L216
[2] https://github.com/hashicorp/nomad/blob/v0.8.7/client/driver/docker.go#L698-L700
2019-02-13 17:55:48 +00:00
ImageName string ` codec:"image" `
Command string ` codec:"command" `
Args [ ] string ` codec:"args" `
TrustPrefix string ` codec:"trust_prefix" `
DNSServers [ ] string ` codec:"dns_servers" ` // DNS Server for containers
DNSSearchDomains [ ] string ` codec:"dns_search_domains" ` // DNS Search domains for containers
Net [ ] string ` codec:"net" ` // Networks for the containers
PortMap hclutils . MapStrStr ` codec:"port_map" ` // A map of host port and the port name defined in the image manifest file
Volumes [ ] string ` codec:"volumes" ` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container[:readOnly]
InsecureOptions [ ] string ` codec:"insecure_options" ` // list of args for --insecure-options
2018-10-17 00:14:21 +00:00
NoOverlay bool ` codec:"no_overlay" ` // disable overlayfs for rkt run
Debug bool ` codec:"debug" ` // Enable debug option for rkt command
Group string ` codec:"group" ` // Group override for the container
2018-10-12 17:37:28 +00:00
}
2018-10-30 21:05:31 +00:00
// TaskState is the state which is encoded in the handle returned in
2018-10-12 17:37:28 +00:00
// StartTask. This information is needed to rebuild the taskConfig state and handler
// during recovery.
2018-10-30 21:05:31 +00:00
type TaskState struct {
2019-01-15 01:02:44 +00:00
ReattachConfig * pstructs . ReattachConfig
2018-10-12 17:37:28 +00:00
TaskConfig * drivers . TaskConfig
Pid int
StartedAt time . Time
UUID string
}
2018-10-30 21:05:31 +00:00
// Driver is a driver for running images via Rkt We attempt to chose sane
// defaults for now, with more configuration available planned in the future.
type Driver struct {
2018-10-05 19:12:03 +00:00
// eventer is used to handle multiplexing of TaskEvents calls such that an
// event can be broadcast to all callers
2018-10-12 17:37:28 +00:00
eventer * eventer . Eventer
2018-10-05 19:12:03 +00:00
// config is the driver configuration set by the SetConfig RPC
config * Config
2018-10-17 03:00:26 +00:00
// nomadConfig is the client config from nomad
2018-10-30 01:34:34 +00:00
nomadConfig * base . ClientDriverConfig
2018-10-17 03:00:26 +00:00
2018-10-15 22:59:25 +00:00
// tasks is the in memory datastore mapping taskIDs to rktTaskHandles
2018-10-05 19:12:03 +00:00
tasks * taskStore
// ctx is the context for the driver. It is passed to other subsystems to
// coordinate shutdown
ctx context . Context
// signalShutdown is called when the driver is shutting down and cancels the
// ctx passed to any subsystems
signalShutdown context . CancelFunc
2018-10-30 21:05:31 +00:00
// logger will log to the Nomad agent
2018-10-05 19:12:03 +00:00
logger hclog . Logger
2018-12-19 23:32:43 +00:00
2019-01-14 21:33:42 +00:00
// A tri-state boolean to know if the fingerprinting has happened and
// whether it has been successful
fingerprintSuccess * bool
fingerprintLock sync . Mutex
2018-10-05 19:12:03 +00:00
}
2018-10-12 17:37:28 +00:00
func NewRktDriver ( logger hclog . Logger ) drivers . DriverPlugin {
ctx , cancel := context . WithCancel ( context . Background ( ) )
logger = logger . Named ( pluginName )
2018-10-30 21:05:31 +00:00
return & Driver {
2018-10-12 17:37:28 +00:00
eventer : eventer . NewEventer ( ctx , logger ) ,
config : & Config { } ,
tasks : newTaskStore ( ) ,
ctx : ctx ,
signalShutdown : cancel ,
logger : logger ,
}
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) PluginInfo ( ) ( * base . PluginInfoResponse , error ) {
2018-10-05 19:12:03 +00:00
return pluginInfo , nil
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) ConfigSchema ( ) ( * hclspec . Spec , error ) {
2018-10-05 19:12:03 +00:00
return configSpec , nil
}
2018-12-18 00:40:58 +00:00
func ( d * Driver ) SetConfig ( cfg * base . Config ) error {
2018-10-05 19:12:03 +00:00
var config Config
2018-12-18 00:40:58 +00:00
if len ( cfg . PluginConfig ) != 0 {
if err := base . MsgPackDecode ( cfg . PluginConfig , & config ) ; err != nil {
return err
}
2018-10-05 19:12:03 +00:00
}
d . config = & config
2018-12-18 00:40:58 +00:00
if cfg . AgentConfig != nil {
d . nomadConfig = cfg . AgentConfig . Driver
2018-10-19 03:32:17 +00:00
}
2018-10-05 19:12:03 +00:00
return nil
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) TaskConfigSchema ( ) ( * hclspec . Spec , error ) {
2018-10-05 19:12:03 +00:00
return taskConfigSpec , nil
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) Capabilities ( ) ( * drivers . Capabilities , error ) {
2018-10-05 19:12:03 +00:00
return capabilities , nil
}
2018-10-31 18:54:29 +00:00
func ( d * Driver ) Fingerprint ( ctx context . Context ) ( <- chan * drivers . Fingerprint , error ) {
2018-10-05 19:12:03 +00:00
ch := make ( chan * drivers . Fingerprint )
2018-10-31 18:54:29 +00:00
go d . handleFingerprint ( ctx , ch )
2018-10-05 19:12:03 +00:00
return ch , nil
}
2018-10-12 17:37:28 +00:00
2018-10-30 21:05:31 +00:00
func ( d * Driver ) handleFingerprint ( ctx context . Context , ch chan * drivers . Fingerprint ) {
2018-10-15 17:27:14 +00:00
defer close ( ch )
2018-10-05 19:12:03 +00:00
ticker := time . NewTimer ( 0 )
for {
select {
case <- ctx . Done ( ) :
return
case <- d . ctx . Done ( ) :
return
case <- ticker . C :
ticker . Reset ( fingerprintPeriod )
ch <- d . buildFingerprint ( )
}
}
}
2019-01-14 21:33:42 +00:00
// setFingerprintSuccess marks the driver as having fingerprinted successfully
func ( d * Driver ) setFingerprintSuccess ( ) {
2019-01-07 22:47:49 +00:00
d . fingerprintLock . Lock ( )
2019-01-14 21:33:42 +00:00
d . fingerprintSuccess = helper . BoolToPtr ( true )
2019-01-07 22:47:49 +00:00
d . fingerprintLock . Unlock ( )
}
2019-01-14 21:33:42 +00:00
// setFingerprintFailure marks the driver as having failed fingerprinting
func ( d * Driver ) setFingerprintFailure ( ) {
2019-01-07 22:47:49 +00:00
d . fingerprintLock . Lock ( )
2019-01-14 21:33:42 +00:00
d . fingerprintSuccess = helper . BoolToPtr ( false )
d . fingerprintLock . Unlock ( )
2019-01-07 22:47:49 +00:00
}
2019-01-16 17:04:11 +00:00
// fingerprintSuccessful returns true if the driver has
// never fingerprinted or has successfully fingerprinted
func ( d * Driver ) fingerprintSuccessful ( ) bool {
d . fingerprintLock . Lock ( )
defer d . fingerprintLock . Unlock ( )
return d . fingerprintSuccess == nil || * d . fingerprintSuccess
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) buildFingerprint ( ) * drivers . Fingerprint {
2018-10-05 19:12:03 +00:00
fingerprint := & drivers . Fingerprint {
2018-11-21 00:30:39 +00:00
Attributes : map [ string ] * pstructs . Attribute { } ,
2018-10-05 19:12:03 +00:00
Health : drivers . HealthStateHealthy ,
2019-01-07 04:04:15 +00:00
HealthDescription : drivers . DriverHealthy ,
2018-10-05 19:12:03 +00:00
}
2018-10-15 22:49:19 +00:00
// Only enable if we are root
if syscall . Geteuid ( ) != 0 {
2019-01-16 17:04:11 +00:00
if d . fingerprintSuccessful ( ) {
2018-12-19 23:32:43 +00:00
d . logger . Debug ( "must run as root user, disabling" )
}
2019-01-14 21:33:42 +00:00
d . setFingerprintFailure ( )
2018-10-05 19:12:03 +00:00
fingerprint . Health = drivers . HealthStateUndetected
2018-12-20 11:55:23 +00:00
fingerprint . HealthDescription = drivers . DriverRequiresRootMessage
2018-10-05 19:12:03 +00:00
return fingerprint
}
outBytes , err := exec . Command ( rktCmd , "version" ) . Output ( )
if err != nil {
fingerprint . Health = drivers . HealthStateUndetected
2019-01-07 04:04:15 +00:00
fingerprint . HealthDescription = fmt . Sprintf ( "Failed to execute %s version: %v" , rktCmd , err )
2019-01-14 21:33:42 +00:00
d . setFingerprintFailure ( )
2018-10-05 19:12:03 +00:00
return fingerprint
}
out := strings . TrimSpace ( string ( outBytes ) )
rktMatches := reRktVersion . FindStringSubmatch ( out )
appcMatches := reAppcVersion . FindStringSubmatch ( out )
if len ( rktMatches ) != 2 || len ( appcMatches ) != 2 {
fingerprint . Health = drivers . HealthStateUndetected
2019-01-07 04:04:15 +00:00
fingerprint . HealthDescription = "Unable to parse rkt version string"
2019-01-14 21:33:42 +00:00
d . setFingerprintFailure ( )
2018-10-05 19:12:03 +00:00
return fingerprint
}
minVersion , _ := version . NewVersion ( minRktVersion )
currentVersion , _ := version . NewVersion ( rktMatches [ 1 ] )
if currentVersion . LessThan ( minVersion ) {
// Do not allow ancient rkt versions
fingerprint . Health = drivers . HealthStateUndetected
2019-01-07 04:04:15 +00:00
fingerprint . HealthDescription = fmt . Sprintf ( "Unsuported rkt version %s" , currentVersion )
2019-01-16 17:04:11 +00:00
if d . fingerprintSuccessful ( ) {
2018-12-19 23:32:43 +00:00
d . logger . Warn ( "unsupported rkt version please upgrade to >= " + minVersion . String ( ) ,
"rkt_version" , currentVersion )
}
2019-01-14 21:33:42 +00:00
d . setFingerprintFailure ( )
2018-10-05 19:12:03 +00:00
return fingerprint
}
2018-11-21 00:30:39 +00:00
fingerprint . Attributes [ "driver.rkt" ] = pstructs . NewBoolAttribute ( true )
fingerprint . Attributes [ "driver.rkt.version" ] = pstructs . NewStringAttribute ( rktMatches [ 1 ] )
fingerprint . Attributes [ "driver.rkt.appc.version" ] = pstructs . NewStringAttribute ( appcMatches [ 1 ] )
2018-10-05 19:12:03 +00:00
if d . config . VolumesEnabled {
2018-11-21 00:30:39 +00:00
fingerprint . Attributes [ "driver.rkt.volumes.enabled" ] = pstructs . NewBoolAttribute ( true )
2018-10-05 19:12:03 +00:00
}
2019-01-14 21:33:42 +00:00
d . setFingerprintSuccess ( )
2018-10-05 19:12:03 +00:00
return fingerprint
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) RecoverTask ( handle * drivers . TaskHandle ) error {
2018-10-12 17:37:28 +00:00
if handle == nil {
return fmt . Errorf ( "error: handle cannot be nil" )
}
2019-01-23 19:44:42 +00:00
// COMPAT(0.10): pre 0.9 upgrade path check
2019-01-16 16:19:25 +00:00
if handle . Version == 0 {
2019-01-23 19:44:42 +00:00
return d . recoverPre09Task ( handle )
2019-01-16 16:19:25 +00:00
}
2018-10-18 20:39:02 +00:00
// If already attached to handle there's nothing to recover.
if _ , ok := d . tasks . Get ( handle . Config . ID ) ; ok {
d . logger . Trace ( "nothing to recover; task already exists" ,
"task_id" , handle . Config . ID ,
"task_name" , handle . Config . Name ,
)
return nil
}
2018-10-30 21:05:31 +00:00
var taskState TaskState
2018-10-12 17:37:28 +00:00
if err := handle . GetDriverState ( & taskState ) ; err != nil {
d . logger . Error ( "failed to decode taskConfig state from handle" , "error" , err , "task_id" , handle . Config . ID )
return fmt . Errorf ( "failed to decode taskConfig state from handle: %v" , err )
}
2019-01-15 01:02:44 +00:00
plugRC , err := pstructs . ReattachConfigToGoPlugin ( taskState . ReattachConfig )
2018-10-12 17:37:28 +00:00
if err != nil {
d . logger . Error ( "failed to build ReattachConfig from taskConfig state" , "error" , err , "task_id" , handle . Config . ID )
return fmt . Errorf ( "failed to build ReattachConfig from taskConfig state: %v" , err )
}
2019-01-14 17:25:59 +00:00
execImpl , pluginClient , err := executor . ReattachToExecutor ( plugRC ,
2019-01-10 00:52:06 +00:00
d . logger . With ( "task_name" , handle . Config . Name , "alloc_id" , handle . Config . AllocID ) )
2018-10-12 17:37:28 +00:00
if err != nil {
d . logger . Error ( "failed to reattach to executor" , "error" , err , "task_id" , handle . Config . ID )
return fmt . Errorf ( "failed to reattach to executor: %v" , err )
}
// The taskConfig's environment is set via --set-env flags in Start, but the rkt
// command itself needs an environment with PATH set to find iptables.
// TODO (preetha) need to figure out how to read env.blacklist
2018-11-30 11:18:39 +00:00
eb := taskenv . NewEmptyBuilder ( )
2018-10-12 17:37:28 +00:00
filter := strings . Split ( config . DefaultEnvBlacklist , "," )
rktEnv := eb . SetHostEnvvars ( filter ) . Build ( )
2018-10-31 18:54:29 +00:00
h := & taskHandle {
2018-10-12 17:37:28 +00:00
exec : execImpl ,
env : rktEnv ,
pid : taskState . Pid ,
uuid : taskState . UUID ,
pluginClient : pluginClient ,
taskConfig : taskState . TaskConfig ,
procState : drivers . TaskStateRunning ,
startedAt : taskState . StartedAt ,
exitResult : & drivers . ExitResult { } ,
}
d . tasks . Set ( taskState . TaskConfig . ID , h )
go h . run ( )
return nil
2018-10-05 19:12:03 +00:00
}
2019-01-04 23:01:35 +00:00
func ( d * Driver ) StartTask ( cfg * drivers . TaskConfig ) ( * drivers . TaskHandle , * drivers . DriverNetwork , error ) {
2018-10-05 19:12:03 +00:00
if _ , ok := d . tasks . Get ( cfg . ID ) ; ok {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "taskConfig with ID '%s' already started" , cfg . ID )
2018-10-05 19:12:03 +00:00
}
var driverConfig TaskConfig
2018-10-15 22:40:38 +00:00
2018-10-05 19:12:03 +00:00
if err := cfg . DecodeDriverConfig ( & driverConfig ) ; err != nil {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "failed to decode driver config: %v" , err )
2018-10-05 19:12:03 +00:00
}
2019-01-17 02:52:31 +00:00
handle := drivers . NewTaskHandle ( taskHandleVersion )
2018-10-05 19:12:03 +00:00
handle . Config = cfg
2018-10-12 17:37:28 +00:00
// todo(preetha) - port map in client v1 is a slice of maps that get merged. figure out if the caller will do this
//driverConfig.PortMap
2018-10-05 19:12:03 +00:00
// ACI image
img := driverConfig . ImageName
// Global arguments given to both prepare and run-prepared
globalArgs := make ( [ ] string , 0 , 50 )
// Add debug option to rkt command.
debug := driverConfig . Debug
// Add the given trust prefix
trustPrefix := driverConfig . TrustPrefix
insecure := false
if trustPrefix != "" {
var outBuf , errBuf bytes . Buffer
cmd := exec . Command ( rktCmd , "trust" , "--skip-fingerprint-review=true" , fmt . Sprintf ( "--prefix=%s" , trustPrefix ) , fmt . Sprintf ( "--debug=%t" , debug ) )
cmd . Stdout = & outBuf
cmd . Stderr = & errBuf
if err := cmd . Run ( ) ; err != nil {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "Error running rkt trust: %s\n\nOutput: %s\n\nError: %s" ,
2018-10-05 19:12:03 +00:00
err , outBuf . String ( ) , errBuf . String ( ) )
}
2018-10-15 22:59:25 +00:00
d . logger . Debug ( "added trust prefix" , "trust_prefix" , trustPrefix , "task_name" , cfg . Name )
2018-10-05 19:12:03 +00:00
} else {
// Disable signature verification if the trust command was not run.
insecure = true
}
// if we have a selective insecure_options, prefer them
// insecure options are rkt's global argument, so we do this before the actual "run"
if len ( driverConfig . InsecureOptions ) > 0 {
globalArgs = append ( globalArgs , fmt . Sprintf ( "--insecure-options=%s" , strings . Join ( driverConfig . InsecureOptions , "," ) ) )
} else if insecure {
globalArgs = append ( globalArgs , "--insecure-options=all" )
}
// debug is rkt's global argument, so add it before the actual "run"
globalArgs = append ( globalArgs , fmt . Sprintf ( "--debug=%t" , debug ) )
prepareArgs := make ( [ ] string , 0 , 50 )
runArgs := make ( [ ] string , 0 , 50 )
prepareArgs = append ( prepareArgs , globalArgs ... )
prepareArgs = append ( prepareArgs , "prepare" )
runArgs = append ( runArgs , globalArgs ... )
runArgs = append ( runArgs , "run-prepared" )
// disable overlayfs
if driverConfig . NoOverlay {
prepareArgs = append ( prepareArgs , "--no-overlay=true" )
}
2018-10-12 17:37:28 +00:00
// Convert underscores to dashes in taskConfig names for use in volume names #2358
2018-10-05 19:12:03 +00:00
sanitizedName := strings . Replace ( cfg . Name , "_" , "-" , - 1 )
// Mount /alloc
2018-11-16 20:36:28 +00:00
allocVolName := fmt . Sprintf ( "%s-%s-alloc" , cfg . AllocID , sanitizedName )
2018-10-05 19:12:03 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s" , allocVolName , cfg . TaskDir ( ) . SharedAllocDir ) )
2018-10-12 17:37:28 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , allocVolName , "/alloc" ) )
2018-10-05 19:12:03 +00:00
// Mount /local
2018-11-16 20:36:28 +00:00
localVolName := fmt . Sprintf ( "%s-%s-local" , cfg . AllocID , sanitizedName )
2018-10-05 19:12:03 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s" , localVolName , cfg . TaskDir ( ) . LocalDir ) )
2018-10-12 17:37:28 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , localVolName , "/local" ) )
2018-10-05 19:12:03 +00:00
// Mount /secrets
2018-11-16 20:36:28 +00:00
secretsVolName := fmt . Sprintf ( "%s-%s-secrets" , cfg . AllocID , sanitizedName )
2018-10-05 19:12:03 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s" , secretsVolName , cfg . TaskDir ( ) . SecretsDir ) )
2018-10-12 17:37:28 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , secretsVolName , "/secrets" ) )
2018-10-05 19:12:03 +00:00
// Mount arbitrary volumes if enabled
if len ( driverConfig . Volumes ) > 0 {
if ! d . config . VolumesEnabled {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "volumes_enabled is false; cannot use rkt volumes: %+q" , driverConfig . Volumes )
2018-10-05 19:12:03 +00:00
}
for i , rawvol := range driverConfig . Volumes {
parts := strings . Split ( rawvol , ":" )
readOnly := "false"
// job spec:
// volumes = ["/host/path:/container/path[:readOnly]"]
// the third parameter is optional, mount is read-write by default
if len ( parts ) == 3 {
if parts [ 2 ] == "readOnly" {
d . logger . Debug ( "mounting volume as readOnly" , "volume" , strings . Join ( parts [ : 2 ] , parts [ 1 ] ) )
readOnly = "true"
} else {
d . logger . Warn ( "unknown volume parameter ignored for mount" , "parameter" , parts [ 2 ] , "mount" , parts [ 0 ] )
}
} else if len ( parts ) != 2 {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "invalid rkt volume: %q" , rawvol )
2018-10-05 19:12:03 +00:00
}
2018-11-16 20:36:28 +00:00
volName := fmt . Sprintf ( "%s-%s-%d" , cfg . AllocID , sanitizedName , i )
2018-10-05 19:12:03 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s,readOnly=%s" , volName , parts [ 0 ] , readOnly ) )
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , volName , parts [ 1 ] ) )
}
}
2018-11-28 19:13:23 +00:00
// Mount task volumes, always do
for i , vol := range cfg . Mounts {
volName := fmt . Sprintf ( "%s-%s-taskmounts-%d" , cfg . AllocID , sanitizedName , i )
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s,readOnly=%v" , volName , vol . HostPath , vol . Readonly ) )
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , volName , vol . TaskPath ) )
}
// Mount task devices, always do
for i , vol := range cfg . Devices {
volName := fmt . Sprintf ( "%s-%s-taskdevices-%d" , cfg . AllocID , sanitizedName , i )
readOnly := ! strings . Contains ( vol . Permissions , "w" )
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--volume=%s,kind=host,source=%s,readOnly=%v" , volName , vol . HostPath , readOnly ) )
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--mount=volume=%s,target=%s" , volName , vol . TaskPath ) )
}
2018-10-05 19:12:03 +00:00
// Inject environment variables
for k , v := range cfg . Env {
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--set-env=%s=%s" , k , v ) )
}
// Image is set here, because the commands that follow apply to it
prepareArgs = append ( prepareArgs , img )
// Check if the user has overridden the exec command.
if driverConfig . Command != "" {
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--exec=%v" , driverConfig . Command ) )
}
// Add memory isolator
2018-10-16 23:42:19 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--memory=%v" , cfg . Resources . LinuxResources . MemoryLimitBytes ) )
2018-10-05 19:12:03 +00:00
// Add CPU isolator
2019-01-15 21:15:28 +00:00
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--cpu=%v" , cfg . Resources . LinuxResources . CPUShares ) )
2018-10-05 19:12:03 +00:00
// Add DNS servers
if len ( driverConfig . DNSServers ) == 1 && ( driverConfig . DNSServers [ 0 ] == "host" || driverConfig . DNSServers [ 0 ] == "none" ) {
// Special case single item lists with the special values "host" or "none"
runArgs = append ( runArgs , fmt . Sprintf ( "--dns=%s" , driverConfig . DNSServers [ 0 ] ) )
} else {
for _ , ip := range driverConfig . DNSServers {
if err := net . ParseIP ( ip ) ; err == nil {
2018-10-12 17:37:28 +00:00
wrappedErr := fmt . Errorf ( "invalid ip address for container dns server %q" , ip )
d . logger . Debug ( "error parsing DNS server" , "error" , wrappedErr )
return nil , nil , wrappedErr
2018-10-05 19:12:03 +00:00
}
runArgs = append ( runArgs , fmt . Sprintf ( "--dns=%s" , ip ) )
}
}
// set DNS search domains
for _ , domain := range driverConfig . DNSSearchDomains {
runArgs = append ( runArgs , fmt . Sprintf ( "--dns-search=%s" , domain ) )
}
// set network
network := strings . Join ( driverConfig . Net , "," )
if network != "" {
runArgs = append ( runArgs , fmt . Sprintf ( "--net=%s" , network ) )
}
// Setup port mapping and exposed ports
2018-10-12 17:37:28 +00:00
if len ( cfg . Resources . NomadResources . Networks ) == 0 {
d . logger . Debug ( "no network interfaces are available" )
2018-10-05 19:12:03 +00:00
if len ( driverConfig . PortMap ) > 0 {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "Trying to map ports but no network interface is available" )
2018-10-05 19:12:03 +00:00
}
} else if network == "host" {
// Port mapping is skipped when host networking is used.
2018-10-15 22:59:25 +00:00
d . logger . Debug ( "Ignoring port_map when using --net=host" , "task_name" , cfg . Name )
2018-10-05 19:12:03 +00:00
} else {
2018-10-12 17:37:28 +00:00
network := cfg . Resources . NomadResources . Networks [ 0 ]
2018-10-05 19:12:03 +00:00
for _ , port := range network . ReservedPorts {
var containerPort string
2018-10-15 22:40:38 +00:00
mapped , ok := driverConfig . PortMap [ port . Label ]
2018-10-05 19:12:03 +00:00
if ! ok {
// If the user doesn't have a mapped port using port_map, driver stops running container.
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "port_map is not set. When you defined port in the resources, you need to configure port_map." )
2018-10-05 19:12:03 +00:00
}
containerPort = mapped
hostPortStr := strconv . Itoa ( port . Value )
2018-10-12 17:37:28 +00:00
d . logger . Debug ( "driver.rkt: exposed port" , "containerPort" , containerPort )
2018-10-05 19:12:03 +00:00
// Add port option to rkt run arguments. rkt allows multiple port args
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--port=%s:%s" , containerPort , hostPortStr ) )
}
for _ , port := range network . DynamicPorts {
// By default we will map the allocated port 1:1 to the container
var containerPort string
2018-10-15 22:40:38 +00:00
if mapped , ok := driverConfig . PortMap [ port . Label ] ; ok {
2018-10-05 19:12:03 +00:00
containerPort = mapped
} else {
// If the user doesn't have mapped a port using port_map, driver stops running container.
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "port_map is not set. When you defined port in the resources, you need to configure port_map." )
2018-10-05 19:12:03 +00:00
}
hostPortStr := strconv . Itoa ( port . Value )
2018-10-15 22:59:25 +00:00
d . logger . Debug ( "exposed port" , "containerPort" , containerPort , "task_name" , cfg . Name )
2018-10-05 19:12:03 +00:00
// Add port option to rkt run arguments. rkt allows multiple port args
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--port=%s:%s" , containerPort , hostPortStr ) )
}
2018-10-12 17:37:28 +00:00
}
2018-10-05 19:12:03 +00:00
2018-10-12 17:37:28 +00:00
// If a user has been specified for the taskConfig, pass it through to the user
2018-10-05 19:12:03 +00:00
if cfg . User != "" {
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--user=%s" , cfg . User ) )
}
2018-10-12 17:37:28 +00:00
// There's no taskConfig-level parameter for groups so check the driver
2018-10-05 19:12:03 +00:00
// config for a custom group
if driverConfig . Group != "" {
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "--group=%s" , driverConfig . Group ) )
}
// Add user passed arguments.
if len ( driverConfig . Args ) != 0 {
// Need to start arguments with "--"
prepareArgs = append ( prepareArgs , "--" )
for _ , arg := range driverConfig . Args {
prepareArgs = append ( prepareArgs , fmt . Sprintf ( "%v" , arg ) )
}
}
pluginLogFile := filepath . Join ( cfg . TaskDir ( ) . Dir , fmt . Sprintf ( "%s-executor.out" , cfg . Name ) )
2018-12-07 02:13:45 +00:00
executorConfig := & executor . ExecutorConfig {
2018-10-05 19:12:03 +00:00
LogFile : pluginLogFile ,
LogLevel : "debug" ,
}
2019-01-10 00:52:06 +00:00
execImpl , pluginClient , err := executor . CreateExecutor (
d . logger . With ( "task_name" , handle . Config . Name , "alloc_id" , handle . Config . AllocID ) ,
d . nomadConfig , executorConfig )
2018-10-05 19:12:03 +00:00
if err != nil {
2018-10-12 17:37:28 +00:00
return nil , nil , err
2018-10-05 19:12:03 +00:00
}
absPath , err := GetAbsolutePath ( rktCmd )
if err != nil {
2018-10-12 17:37:28 +00:00
return nil , nil , err
2018-10-05 19:12:03 +00:00
}
var outBuf , errBuf bytes . Buffer
cmd := exec . Command ( rktCmd , prepareArgs ... )
cmd . Stdout = & outBuf
cmd . Stderr = & errBuf
2018-10-12 17:37:28 +00:00
d . logger . Debug ( "preparing taskConfig" , "pod" , img , "task_name" , cfg . Name , "args" , prepareArgs )
2018-10-05 19:12:03 +00:00
if err := cmd . Run ( ) ; err != nil {
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "Error preparing rkt pod: %s\n\nOutput: %s\n\nError: %s" ,
2018-10-05 19:12:03 +00:00
err , outBuf . String ( ) , errBuf . String ( ) )
}
uuid := strings . TrimSpace ( outBuf . String ( ) )
2018-10-12 17:37:28 +00:00
d . logger . Debug ( "taskConfig prepared" , "pod" , img , "task_name" , cfg . Name , "uuid" , uuid )
2018-10-05 19:12:03 +00:00
runArgs = append ( runArgs , uuid )
2018-10-12 17:37:28 +00:00
// The taskConfig's environment is set via --set-env flags above, but the rkt
// command itself needs an environment with PATH set to find iptables.
// TODO (preetha) need to figure out how to pass env.blacklist from client config
2018-11-30 11:18:39 +00:00
eb := taskenv . NewEmptyBuilder ( )
2018-10-12 17:37:28 +00:00
filter := strings . Split ( config . DefaultEnvBlacklist , "," )
2018-10-05 19:12:03 +00:00
rktEnv := eb . SetHostEnvvars ( filter ) . Build ( )
// Enable ResourceLimits to place the executor in a parent cgroup of
// the rkt container. This allows stats collection via the executor to
// work just like it does for exec.
2018-12-07 01:54:14 +00:00
execCmd := & executor . ExecCommand {
2018-10-05 19:12:03 +00:00
Cmd : absPath ,
Args : runArgs ,
ResourceLimits : true ,
2018-12-07 02:39:53 +00:00
Resources : cfg . Resources ,
2018-12-16 01:25:36 +00:00
// Use rktEnv, the environment needed for running rkt, not the task env
Env : rktEnv . List ( ) ,
TaskDir : cfg . TaskDir ( ) . Dir ,
StdoutPath : cfg . StdoutPath ,
StderrPath : cfg . StderrPath ,
2018-10-05 19:12:03 +00:00
}
2018-10-12 17:37:28 +00:00
ps , err := execImpl . Launch ( execCmd )
2018-10-05 19:12:03 +00:00
if err != nil {
pluginClient . Kill ( )
2018-10-12 17:37:28 +00:00
return nil , nil , err
}
d . logger . Debug ( "started taskConfig" , "aci" , img , "uuid" , uuid , "task_name" , cfg . Name , "args" , runArgs )
2018-10-31 18:54:29 +00:00
h := & taskHandle {
2018-10-12 17:37:28 +00:00
exec : execImpl ,
env : rktEnv ,
pid : ps . Pid ,
uuid : uuid ,
pluginClient : pluginClient ,
taskConfig : cfg ,
procState : drivers . TaskStateRunning ,
startedAt : time . Now ( ) . Round ( time . Millisecond ) ,
logger : d . logger ,
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
rktDriverState := TaskState {
2019-01-15 01:02:44 +00:00
ReattachConfig : pstructs . ReattachConfigFromGoPlugin ( pluginClient . ReattachConfig ( ) ) ,
2018-10-12 17:37:28 +00:00
Pid : ps . Pid ,
TaskConfig : cfg ,
StartedAt : h . startedAt ,
UUID : uuid ,
2018-10-05 19:12:03 +00:00
}
2018-10-12 17:37:28 +00:00
if err := handle . SetDriverState ( & rktDriverState ) ; err != nil {
2018-10-15 22:59:25 +00:00
d . logger . Error ( "failed to start task, error setting driver state" , "error" , err , "task_name" , cfg . Name )
2018-10-12 17:37:28 +00:00
execImpl . Shutdown ( "" , 0 )
pluginClient . Kill ( )
return nil , nil , fmt . Errorf ( "failed to set driver state: %v" , err )
}
d . tasks . Set ( cfg . ID , h )
2018-10-05 19:12:03 +00:00
go h . run ( )
// Do not attempt to retrieve driver network if one won't exist:
// - "host" means the container itself has no networking metadata
// - "none" means no network is configured
// https://coreos.com/rkt/docs/latest/networking/overview.html#no-loopback-only-networking
2019-01-04 23:01:35 +00:00
var driverNetwork * drivers . DriverNetwork
2018-10-05 19:12:03 +00:00
if network != "host" && network != "none" {
2018-10-15 22:59:25 +00:00
d . logger . Debug ( "retrieving network information for pod" , "pod" , img , "UUID" , uuid , "task_name" , cfg . Name )
2018-10-15 22:40:38 +00:00
driverNetwork , err = rktGetDriverNetwork ( uuid , driverConfig . PortMap , d . logger )
2018-10-05 19:12:03 +00:00
if err != nil && ! pluginClient . Exited ( ) {
2018-10-15 22:59:25 +00:00
d . logger . Warn ( "network status retrieval for pod failed" , "pod" , img , "UUID" , uuid , "task_name" , cfg . Name , "error" , err )
2018-10-05 19:12:03 +00:00
// If a portmap was given, this turns into a fatal error
if len ( driverConfig . PortMap ) != 0 {
pluginClient . Kill ( )
2018-10-12 17:37:28 +00:00
return nil , nil , fmt . Errorf ( "Trying to map ports but driver could not determine network information" )
2018-10-05 19:12:03 +00:00
}
}
}
2018-10-12 17:37:28 +00:00
return handle , driverNetwork , nil
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) WaitTask ( ctx context . Context , taskID string ) ( <- chan * drivers . ExitResult , error ) {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return nil , drivers . ErrTaskNotFound
}
ch := make ( chan * drivers . ExitResult )
go d . handleWait ( ctx , handle , ch )
return ch , nil
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) StopTask ( taskID string , timeout time . Duration , signal string ) error {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return drivers . ErrTaskNotFound
}
if err := handle . exec . Shutdown ( signal , timeout ) ; err != nil {
if handle . pluginClient . Exited ( ) {
return nil
}
return fmt . Errorf ( "executor Shutdown failed: %v" , err )
}
return nil
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) DestroyTask ( taskID string , force bool ) error {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return drivers . ErrTaskNotFound
}
if handle . IsRunning ( ) && ! force {
return fmt . Errorf ( "cannot destroy running task" )
}
if ! handle . pluginClient . Exited ( ) {
if handle . IsRunning ( ) {
if err := handle . exec . Shutdown ( "" , 0 ) ; err != nil {
handle . logger . Error ( "destroying executor failed" , "err" , err )
}
}
handle . pluginClient . Kill ( )
}
d . tasks . Delete ( taskID )
return nil
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) InspectTask ( taskID string ) ( * drivers . TaskStatus , error ) {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return nil , drivers . ErrTaskNotFound
}
2018-10-31 18:54:29 +00:00
return handle . TaskStatus ( ) , nil
2018-10-05 19:12:03 +00:00
}
2018-12-11 20:27:50 +00:00
func ( d * Driver ) TaskStats ( ctx context . Context , taskID string , interval time . Duration ) ( <- chan * drivers . TaskResourceUsage , error ) {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return nil , drivers . ErrTaskNotFound
}
2018-12-11 20:27:50 +00:00
return handle . exec . Stats ( ctx , interval )
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) TaskEvents ( ctx context . Context ) ( <- chan * drivers . TaskEvent , error ) {
2018-10-12 17:37:28 +00:00
return d . eventer . TaskEvents ( ctx )
2018-10-05 19:12:03 +00:00
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) SignalTask ( taskID string , signal string ) error {
2018-10-12 17:37:28 +00:00
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return drivers . ErrTaskNotFound
}
sig := os . Interrupt
if s , ok := signals . SignalLookup [ signal ] ; ok {
sig = s
2019-03-25 21:19:43 +00:00
} else {
d . logger . Warn ( "unknown signal to send to task, using SIGINT instead" , "signal" , signal , "task_id" , handle . taskConfig . ID , "task_name" , handle . taskConfig . Name )
2018-10-12 17:37:28 +00:00
}
return handle . exec . Signal ( sig )
}
2018-10-30 21:05:31 +00:00
func ( d * Driver ) ExecTask ( taskID string , cmdArgs [ ] string , timeout time . Duration ) ( * drivers . ExecTaskResult , error ) {
2018-10-12 17:37:28 +00:00
if len ( cmdArgs ) == 0 {
return nil , fmt . Errorf ( "error cmd must have atleast one value" )
}
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return nil , drivers . ErrTaskNotFound
}
// enter + UUID + cmd + args...
cmd := cmdArgs [ 0 ]
args := cmdArgs [ 1 : ]
enterArgs := make ( [ ] string , 3 + len ( args ) )
enterArgs [ 0 ] = "enter"
enterArgs [ 1 ] = handle . uuid
enterArgs [ 2 ] = handle . env . ReplaceEnv ( cmd )
copy ( enterArgs [ 3 : ] , handle . env . ParseAndReplace ( args ) )
out , exitCode , err := handle . exec . Exec ( time . Now ( ) . Add ( timeout ) , rktCmd , enterArgs )
if err != nil {
return nil , err
}
return & drivers . ExecTaskResult {
Stdout : out ,
ExitResult : & drivers . ExitResult {
ExitCode : exitCode ,
} ,
} , nil
}
2019-05-12 22:26:14 +00:00
var _ drivers . ExecTaskStreamingRawDriver = ( * Driver ) ( nil )
func ( d * Driver ) ExecTaskStreamingRaw ( ctx context . Context ,
taskID string ,
command [ ] string ,
tty bool ,
stream drivers . ExecTaskStream ) error {
if len ( command ) == 0 {
return fmt . Errorf ( "error cmd must have atleast one value" )
}
handle , ok := d . tasks . Get ( taskID )
if ! ok {
return drivers . ErrTaskNotFound
}
enterCmd := [ ] string { rktCmd , "enter" , handle . uuid , handle . env . ReplaceEnv ( command [ 0 ] ) }
enterCmd = append ( enterCmd , handle . env . ParseAndReplace ( command [ 1 : ] ) ... )
return handle . exec . ExecStreaming ( ctx , enterCmd , tty , stream )
}
2018-10-12 17:37:28 +00:00
// GetAbsolutePath returns the absolute path of the passed binary by resolving
// it in the path and following symlinks.
func GetAbsolutePath ( bin string ) ( string , error ) {
lp , err := exec . LookPath ( bin )
if err != nil {
return "" , fmt . Errorf ( "failed to resolve path to %q executable: %v" , bin , err )
}
return filepath . EvalSymlinks ( lp )
}
2019-01-04 23:01:35 +00:00
func rktGetDriverNetwork ( uuid string , driverConfigPortMap map [ string ] string , logger hclog . Logger ) ( * drivers . DriverNetwork , error ) {
2018-10-30 21:05:31 +00:00
deadline := time . Now ( ) . Add ( networkDeadline )
2018-10-12 17:37:28 +00:00
var lastErr error
try := 0
for time . Now ( ) . Before ( deadline ) {
try ++
if status , err := rktGetStatus ( uuid , logger ) ; err == nil {
for _ , net := range status . Networks {
if ! net . IP . IsGlobalUnicast ( ) {
continue
}
// Get the pod manifest so we can figure out which ports are exposed
var portmap map [ string ] int
manifest , err := rktGetManifest ( uuid )
if err == nil {
portmap , err = rktManifestMakePortMap ( manifest , driverConfigPortMap )
if err != nil {
lastErr = fmt . Errorf ( "could not create manifest-based portmap: %v" , err )
return nil , lastErr
}
} else {
lastErr = fmt . Errorf ( "could not get pod manifest: %v" , err )
return nil , lastErr
}
// This is a successful landing; log if its not the first attempt.
if try > 1 {
logger . Debug ( "retrieved network info for pod" , "uuid" , uuid , "attempt" , try )
}
2019-01-04 23:01:35 +00:00
return & drivers . DriverNetwork {
2018-10-12 17:37:28 +00:00
PortMap : portmap ,
IP : status . Networks [ 0 ] . IP . String ( ) ,
} , nil
}
if len ( status . Networks ) == 0 {
lastErr = fmt . Errorf ( "no networks found" )
} else {
lastErr = fmt . Errorf ( "no good driver networks out of %d returned" , len ( status . Networks ) )
}
} else {
lastErr = fmt . Errorf ( "getting status failed: %v" , err )
}
waitTime := getJitteredNetworkRetryTime ( )
logger . Debug ( "failed getting network info for pod, sleeping" , "uuid" , uuid , "attempt" , try , "err" , lastErr , "wait" , waitTime )
time . Sleep ( waitTime )
}
return nil , fmt . Errorf ( "timed out, last error: %v" , lastErr )
2018-10-05 19:12:03 +00:00
}
2018-10-12 17:37:28 +00:00
// Given a rkt/appc pod manifest and driver portmap configuration, create
// a driver portmap.
func rktManifestMakePortMap ( manifest * appcschema . PodManifest , configPortMap map [ string ] string ) ( map [ string ] int , error ) {
if len ( manifest . Apps ) == 0 {
return nil , fmt . Errorf ( "manifest has no apps" )
}
if len ( manifest . Apps ) != 1 {
return nil , fmt . Errorf ( "manifest has multiple apps!" )
}
app := manifest . Apps [ 0 ]
if app . App == nil {
return nil , fmt . Errorf ( "specified app has no App object" )
}
portMap := make ( map [ string ] int )
for svc , name := range configPortMap {
for _ , port := range app . App . Ports {
if port . Name . String ( ) == name {
portMap [ svc ] = int ( port . Port )
}
}
}
return portMap , nil
}
// Retrieve pod status for the pod with the given UUID.
2019-01-19 20:00:31 +00:00
func rktGetStatus ( uuid string , logger hclog . Logger ) ( * Pod , error ) {
2018-10-12 17:37:28 +00:00
statusArgs := [ ] string {
"status" ,
"--format=json" ,
uuid ,
}
var outBuf , errBuf bytes . Buffer
cmd := exec . Command ( rktCmd , statusArgs ... )
cmd . Stdout = & outBuf
cmd . Stderr = & errBuf
if err := cmd . Run ( ) ; err != nil {
if outBuf . Len ( ) > 0 {
2019-01-09 14:22:47 +00:00
logger . Debug ( "status output for UUID" , "uuid" , uuid , "error" , elide ( outBuf ) )
2018-10-12 17:37:28 +00:00
}
if errBuf . Len ( ) == 0 {
return nil , err
}
logger . Debug ( "status error output" , "uuid" , uuid , "error" , elide ( errBuf ) )
return nil , fmt . Errorf ( "%s. stderr: %q" , err , elide ( errBuf ) )
}
2019-01-19 20:00:31 +00:00
var status Pod
2018-10-12 17:37:28 +00:00
if err := json . Unmarshal ( outBuf . Bytes ( ) , & status ) ; err != nil {
return nil , err
}
return & status , nil
}
// Retrieves a pod manifest
func rktGetManifest ( uuid string ) ( * appcschema . PodManifest , error ) {
statusArgs := [ ] string {
"cat-manifest" ,
uuid ,
}
var outBuf bytes . Buffer
cmd := exec . Command ( rktCmd , statusArgs ... )
cmd . Stdout = & outBuf
cmd . Stderr = ioutil . Discard
if err := cmd . Run ( ) ; err != nil {
return nil , err
}
var manifest appcschema . PodManifest
if err := json . Unmarshal ( outBuf . Bytes ( ) , & manifest ) ; err != nil {
return nil , err
}
return & manifest , nil
}
// Create a time with a 0 to 100ms jitter for rktGetDriverNetwork retries
func getJitteredNetworkRetryTime ( ) time . Duration {
return time . Duration ( 900 + rand . Intn ( 100 ) ) * time . Millisecond
}
// Conditionally elide a buffer to an arbitrary length
func elideToLen ( inBuf bytes . Buffer , length int ) bytes . Buffer {
if inBuf . Len ( ) > length {
inBuf . Truncate ( length )
inBuf . WriteString ( "..." )
}
return inBuf
}
// Conditionally elide a buffer to an 80 character string
func elide ( inBuf bytes . Buffer ) string {
tempBuf := elideToLen ( inBuf , 80 )
return tempBuf . String ( )
}
2018-10-31 18:54:29 +00:00
func ( d * Driver ) handleWait ( ctx context . Context , handle * taskHandle , ch chan * drivers . ExitResult ) {
2018-10-12 17:37:28 +00:00
defer close ( ch )
var result * drivers . ExitResult
2018-12-05 16:04:18 +00:00
ps , err := handle . exec . Wait ( ctx )
2018-10-12 17:37:28 +00:00
if err != nil {
result = & drivers . ExitResult {
Err : fmt . Errorf ( "executor: error waiting on process: %v" , err ) ,
}
} else {
result = & drivers . ExitResult {
ExitCode : ps . ExitCode ,
Signal : ps . Signal ,
}
}
select {
case <- ctx . Done ( ) :
case <- d . ctx . Done ( ) :
case ch <- result :
}
2018-10-05 19:12:03 +00:00
}
2018-12-20 12:25:07 +00:00
func ( d * Driver ) Shutdown ( ) {
d . signalShutdown ( )
}