mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #27369 from cezarsa/hc
Add --health-* flags to service create and update
This commit is contained in:
commit
f860289131
21 changed files with 1526 additions and 450 deletions
|
@ -3,20 +3,22 @@ package swarm
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
// ContainerSpec represents the spec of a container.
|
||||
type ContainerSpec struct {
|
||||
Image string `json:",omitempty"`
|
||||
Labels map[string]string `json:",omitempty"`
|
||||
Command []string `json:",omitempty"`
|
||||
Args []string `json:",omitempty"`
|
||||
Env []string `json:",omitempty"`
|
||||
Dir string `json:",omitempty"`
|
||||
User string `json:",omitempty"`
|
||||
Groups []string `json:",omitempty"`
|
||||
TTY bool `json:",omitempty"`
|
||||
Mounts []mount.Mount `json:",omitempty"`
|
||||
StopGracePeriod *time.Duration `json:",omitempty"`
|
||||
Image string `json:",omitempty"`
|
||||
Labels map[string]string `json:",omitempty"`
|
||||
Command []string `json:",omitempty"`
|
||||
Args []string `json:",omitempty"`
|
||||
Env []string `json:",omitempty"`
|
||||
Dir string `json:",omitempty"`
|
||||
User string `json:",omitempty"`
|
||||
Groups []string `json:",omitempty"`
|
||||
TTY bool `json:",omitempty"`
|
||||
Mounts []mount.Mount `json:",omitempty"`
|
||||
StopGracePeriod *time.Duration `json:",omitempty"`
|
||||
Healthcheck *container.HealthConfig `json:",omitempty"`
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/opts"
|
||||
|
@ -68,6 +69,25 @@ func (c *nanoCPUs) Value() int64 {
|
|||
return int64(*c)
|
||||
}
|
||||
|
||||
// PositiveDurationOpt is an option type for time.Duration that uses a pointer.
|
||||
// It bahave similarly to DurationOpt but only allows positive duration values.
|
||||
type PositiveDurationOpt struct {
|
||||
DurationOpt
|
||||
}
|
||||
|
||||
// Set a new value on the option. Setting a negative duration value will cause
|
||||
// an error to be returned.
|
||||
func (d *PositiveDurationOpt) Set(s string) error {
|
||||
err := d.DurationOpt.Set(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *d.DurationOpt.value < 0 {
|
||||
return fmt.Errorf("duration cannot be negative")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DurationOpt is an option type for time.Duration that uses a pointer. This
|
||||
// allows us to get nil values outside, instead of defaulting to 0
|
||||
type DurationOpt struct {
|
||||
|
@ -377,6 +397,47 @@ func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
|
|||
}
|
||||
}
|
||||
|
||||
type healthCheckOptions struct {
|
||||
cmd string
|
||||
interval PositiveDurationOpt
|
||||
timeout PositiveDurationOpt
|
||||
retries int
|
||||
noHealthcheck bool
|
||||
}
|
||||
|
||||
func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) {
|
||||
var healthConfig *container.HealthConfig
|
||||
haveHealthSettings := opts.cmd != "" ||
|
||||
opts.interval.Value() != nil ||
|
||||
opts.timeout.Value() != nil ||
|
||||
opts.retries != 0
|
||||
if opts.noHealthcheck {
|
||||
if haveHealthSettings {
|
||||
return nil, fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
|
||||
}
|
||||
healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
|
||||
} else if haveHealthSettings {
|
||||
var test []string
|
||||
if opts.cmd != "" {
|
||||
test = []string{"CMD-SHELL", opts.cmd}
|
||||
}
|
||||
var interval, timeout time.Duration
|
||||
if ptr := opts.interval.Value(); ptr != nil {
|
||||
interval = *ptr
|
||||
}
|
||||
if ptr := opts.timeout.Value(); ptr != nil {
|
||||
timeout = *ptr
|
||||
}
|
||||
healthConfig = &container.HealthConfig{
|
||||
Test: test,
|
||||
Interval: interval,
|
||||
Timeout: timeout,
|
||||
Retries: opts.retries,
|
||||
}
|
||||
}
|
||||
return healthConfig, nil
|
||||
}
|
||||
|
||||
// ValidatePort validates a string is in the expected format for a port definition
|
||||
func ValidatePort(value string) (string, error) {
|
||||
portMappings, err := nat.ParsePortSpec(value)
|
||||
|
@ -416,6 +477,8 @@ type serviceOptions struct {
|
|||
registryAuth bool
|
||||
|
||||
logDriver logDriverOptions
|
||||
|
||||
healthcheck healthCheckOptions
|
||||
}
|
||||
|
||||
func newServiceOptions() *serviceOptions {
|
||||
|
@ -490,6 +553,12 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|||
EndpointSpec: opts.endpoint.ToEndpointSpec(),
|
||||
}
|
||||
|
||||
healthConfig, err := opts.healthcheck.toHealthConfig()
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
service.TaskTemplate.ContainerSpec.Healthcheck = healthConfig
|
||||
|
||||
switch opts.mode {
|
||||
case "global":
|
||||
if opts.replicas.Value() != nil {
|
||||
|
@ -541,6 +610,12 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
|||
|
||||
flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service")
|
||||
flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options")
|
||||
|
||||
flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
|
||||
flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check")
|
||||
flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run")
|
||||
flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
|
||||
flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -589,4 +664,9 @@ const (
|
|||
flagRegistryAuth = "with-registry-auth"
|
||||
flagLogDriver = "log-driver"
|
||||
flagLogOpt = "log-opt"
|
||||
flagHealthCmd = "health-cmd"
|
||||
flagHealthInterval = "health-interval"
|
||||
flagHealthRetries = "health-retries"
|
||||
flagHealthTimeout = "health-timeout"
|
||||
flagNoHealthcheck = "no-healthcheck"
|
||||
)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
@ -40,6 +42,15 @@ func TestDurationOptSetAndValue(t *testing.T) {
|
|||
var duration DurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Equal(t, *duration.Value(), time.Duration(300*10e8))
|
||||
assert.NilError(t, duration.Set("-300s"))
|
||||
assert.Equal(t, *duration.Value(), time.Duration(-300*10e8))
|
||||
}
|
||||
|
||||
func TestPositiveDurationOptSetAndValue(t *testing.T) {
|
||||
var duration PositiveDurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Equal(t, *duration.Value(), time.Duration(300*10e8))
|
||||
assert.Error(t, duration.Set("-300s"), "cannot be negative")
|
||||
}
|
||||
|
||||
func TestUint64OptString(t *testing.T) {
|
||||
|
@ -201,3 +212,41 @@ func TestMountOptTypeConflict(t *testing.T) {
|
|||
assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
|
||||
assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix")
|
||||
}
|
||||
|
||||
func TestHealthCheckOptionsToHealthConfig(t *testing.T) {
|
||||
dur := time.Second
|
||||
opt := healthCheckOptions{
|
||||
cmd: "curl",
|
||||
interval: PositiveDurationOpt{DurationOpt{value: &dur}},
|
||||
timeout: PositiveDurationOpt{DurationOpt{value: &dur}},
|
||||
retries: 10,
|
||||
}
|
||||
config, err := opt.toHealthConfig()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, reflect.DeepEqual(config, &container.HealthConfig{
|
||||
Test: []string{"CMD-SHELL", "curl"},
|
||||
Interval: time.Second,
|
||||
Timeout: time.Second,
|
||||
Retries: 10,
|
||||
}), true)
|
||||
}
|
||||
|
||||
func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) {
|
||||
opt := healthCheckOptions{
|
||||
noHealthcheck: true,
|
||||
}
|
||||
config, err := opt.toHealthConfig()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, reflect.DeepEqual(config, &container.HealthConfig{
|
||||
Test: []string{"NONE"},
|
||||
}), true)
|
||||
}
|
||||
|
||||
func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
|
||||
opt := healthCheckOptions{
|
||||
cmd: "curl",
|
||||
noHealthcheck: true,
|
||||
}
|
||||
_, err := opt.toHealthConfig()
|
||||
assert.Error(t, err, "--no-healthcheck conflicts with --health-* options")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
|
@ -266,6 +267,10 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
|||
spec.TaskTemplate.ForceUpdate++
|
||||
}
|
||||
|
||||
if err := updateHealthcheck(flags, cspec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -537,3 +542,48 @@ func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
|
||||
if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
|
||||
return nil
|
||||
}
|
||||
if containerSpec.Healthcheck == nil {
|
||||
containerSpec.Healthcheck = &container.HealthConfig{}
|
||||
}
|
||||
noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if noHealthcheck {
|
||||
if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
|
||||
containerSpec.Healthcheck = &container.HealthConfig{
|
||||
Test: []string{"NONE"},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
|
||||
}
|
||||
if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
|
||||
containerSpec.Healthcheck.Test = nil
|
||||
}
|
||||
if flags.Changed(flagHealthInterval) {
|
||||
val := *flags.Lookup(flagHealthInterval).Value.(*PositiveDurationOpt).Value()
|
||||
containerSpec.Healthcheck.Interval = val
|
||||
}
|
||||
if flags.Changed(flagHealthTimeout) {
|
||||
val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value()
|
||||
containerSpec.Healthcheck.Timeout = val
|
||||
}
|
||||
if flags.Changed(flagHealthRetries) {
|
||||
containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
|
||||
}
|
||||
if flags.Changed(flagHealthCmd) {
|
||||
cmd, _ := flags.GetString(flagHealthCmd)
|
||||
if cmd != "" {
|
||||
containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
|
||||
} else {
|
||||
containerSpec.Healthcheck.Test = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
|
@ -196,3 +199,79 @@ func TestUpdatePortsConflictingFlags(t *testing.T) {
|
|||
err := updatePorts(flags, &portConfigs)
|
||||
assert.Error(t, err, "conflicting port mapping")
|
||||
}
|
||||
|
||||
func TestUpdateHealthcheckTable(t *testing.T) {
|
||||
type test struct {
|
||||
flags [][2]string
|
||||
initial *container.HealthConfig
|
||||
expected *container.HealthConfig
|
||||
err string
|
||||
}
|
||||
testCases := []test{
|
||||
{
|
||||
flags: [][2]string{{"no-healthcheck", "true"}},
|
||||
initial: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}, Retries: 10},
|
||||
expected: &container.HealthConfig{Test: []string{"NONE"}},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-cmd", "cmd1"}},
|
||||
initial: &container.HealthConfig{Test: []string{"NONE"}},
|
||||
expected: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-retries", "10"}},
|
||||
initial: &container.HealthConfig{Test: []string{"NONE"}},
|
||||
expected: &container.HealthConfig{Retries: 10},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-retries", "10"}},
|
||||
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
||||
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-interval", "1m"}},
|
||||
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
||||
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Interval: time.Minute},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-cmd", ""}},
|
||||
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
||||
expected: &container.HealthConfig{Retries: 10},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-retries", "0"}},
|
||||
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
||||
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-cmd", "cmd1"}, {"no-healthcheck", "true"}},
|
||||
err: "--no-healthcheck conflicts with --health-* options",
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-interval", "10m"}, {"no-healthcheck", "true"}},
|
||||
err: "--no-healthcheck conflicts with --health-* options",
|
||||
},
|
||||
{
|
||||
flags: [][2]string{{"health-timeout", "1m"}, {"no-healthcheck", "true"}},
|
||||
err: "--no-healthcheck conflicts with --health-* options",
|
||||
},
|
||||
}
|
||||
for i, c := range testCases {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
for _, flag := range c.flags {
|
||||
flags.Set(flag[0], flag[1])
|
||||
}
|
||||
cspec := &swarm.ContainerSpec{
|
||||
Healthcheck: c.initial,
|
||||
}
|
||||
err := updateHealthcheck(flags, cspec)
|
||||
if c.err != "" {
|
||||
assert.Error(t, err, c.err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
|
||||
t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2574,6 +2574,10 @@ _docker_service_update() {
|
|||
--env -e
|
||||
--force
|
||||
--group-add
|
||||
--health-cmd
|
||||
--health-interval
|
||||
--health-retries
|
||||
--health-timeout
|
||||
--label -l
|
||||
--limit-cpu
|
||||
--limit-memory
|
||||
|
@ -2581,6 +2585,7 @@ _docker_service_update() {
|
|||
--log-opt
|
||||
--mount
|
||||
--network
|
||||
--no-healthcheck
|
||||
--publish -p
|
||||
--replicas
|
||||
--reserve-cpu
|
||||
|
|
|
@ -1089,6 +1089,10 @@ __docker_service_subcommand() {
|
|||
"($help)--endpoint-mode=[Placement constraints]:mode:(dnsrr vip)"
|
||||
"($help)*"{-e=,--env=}"[Set environment variables]:env: "
|
||||
"($help)*--group-add=[Add additional user groups to the container]:group:_groups"
|
||||
"($help)--health-cmd=[Command to run to check health]:command: "
|
||||
"($help)--health-interval=[Time between running the check]:time: "
|
||||
"($help)--health-retries=[Consecutive failures needed to report unhealthy]:retries:(1 2 3 4 5)"
|
||||
"($help)--health-timeout=[Maximum time to allow one check to run]:time: "
|
||||
"($help)*--label=[Service labels]:label: "
|
||||
"($help)--limit-cpu=[Limit CPUs]:value: "
|
||||
"($help)--limit-memory=[Limit Memory]:value: "
|
||||
|
@ -1096,6 +1100,7 @@ __docker_service_subcommand() {
|
|||
"($help)*--log-opt=[Logging driver options]:log driver options:__docker_log_options"
|
||||
"($help)*--mount=[Attach a mount to the service]:mount: "
|
||||
"($help)*--network=[Network attachments]:network: "
|
||||
"($help)--no-healthcheck[Disable any container-specified HEALTHCHECK]"
|
||||
"($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
|
||||
"($help)--replicas=[Number of tasks]:replicas: "
|
||||
"($help)--reserve-cpu=[Reserve CPUs]:value: "
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
container "github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
types "github.com/docker/docker/api/types/swarm"
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
|
@ -56,6 +57,11 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
|||
grace, _ := ptypes.Duration(c.StopGracePeriod)
|
||||
containerSpec.StopGracePeriod = &grace
|
||||
}
|
||||
|
||||
if c.Healthcheck != nil {
|
||||
containerSpec.Healthcheck = healthConfigFromGRPC(c.Healthcheck)
|
||||
}
|
||||
|
||||
return containerSpec
|
||||
}
|
||||
|
||||
|
@ -115,5 +121,29 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
|||
containerSpec.Mounts = append(containerSpec.Mounts, mount)
|
||||
}
|
||||
|
||||
if c.Healthcheck != nil {
|
||||
containerSpec.Healthcheck = healthConfigToGRPC(c.Healthcheck)
|
||||
}
|
||||
|
||||
return containerSpec, nil
|
||||
}
|
||||
|
||||
func healthConfigFromGRPC(h *swarmapi.HealthConfig) *container.HealthConfig {
|
||||
interval, _ := ptypes.Duration(h.Interval)
|
||||
timeout, _ := ptypes.Duration(h.Timeout)
|
||||
return &container.HealthConfig{
|
||||
Test: h.Test,
|
||||
Interval: interval,
|
||||
Timeout: timeout,
|
||||
Retries: int(h.Retries),
|
||||
}
|
||||
}
|
||||
|
||||
func healthConfigToGRPC(h *container.HealthConfig) *swarmapi.HealthConfig {
|
||||
return &swarmapi.HealthConfig{
|
||||
Test: h.Test,
|
||||
Interval: ptypes.DurationProto(h.Interval),
|
||||
Timeout: ptypes.DurationProto(h.Timeout),
|
||||
Retries: int32(h.Retries),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/swarmkit/agent/exec"
|
||||
"github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/protobuf/ptypes"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -124,12 +125,13 @@ func (c *containerConfig) image() string {
|
|||
|
||||
func (c *containerConfig) config() *enginecontainer.Config {
|
||||
config := &enginecontainer.Config{
|
||||
Labels: c.labels(),
|
||||
User: c.spec().User,
|
||||
Env: c.spec().Env,
|
||||
WorkingDir: c.spec().Dir,
|
||||
Image: c.image(),
|
||||
Volumes: c.volumes(),
|
||||
Labels: c.labels(),
|
||||
User: c.spec().User,
|
||||
Env: c.spec().Env,
|
||||
WorkingDir: c.spec().Dir,
|
||||
Image: c.image(),
|
||||
Volumes: c.volumes(),
|
||||
Healthcheck: c.healthcheck(),
|
||||
}
|
||||
|
||||
if len(c.spec().Command) > 0 {
|
||||
|
@ -224,6 +226,21 @@ func (c *containerConfig) binds() []string {
|
|||
return r
|
||||
}
|
||||
|
||||
func (c *containerConfig) healthcheck() *enginecontainer.HealthConfig {
|
||||
hcSpec := c.spec().Healthcheck
|
||||
if hcSpec == nil {
|
||||
return nil
|
||||
}
|
||||
interval, _ := ptypes.Duration(hcSpec.Interval)
|
||||
timeout, _ := ptypes.Duration(hcSpec.Timeout)
|
||||
return &enginecontainer.HealthConfig{
|
||||
Test: hcSpec.Test,
|
||||
Interval: interval,
|
||||
Timeout: timeout,
|
||||
Retries: int(hcSpec.Retries),
|
||||
}
|
||||
}
|
||||
|
||||
func getMountMask(m *api.Mount) string {
|
||||
var maskOpts []string
|
||||
if m.ReadOnly {
|
||||
|
|
|
@ -27,6 +27,10 @@ Options:
|
|||
-e, --env value Set environment variables (default [])
|
||||
--env-file value Read in a file of environment variables (default [])
|
||||
--group-add value Add additional user groups to the container (default [])
|
||||
--health-cmd string Command to run to check health
|
||||
--health-interval duration Time between running the check
|
||||
--health-retries int Consecutive failures needed to report unhealthy
|
||||
--health-timeout duration Maximum time to allow one check to run
|
||||
--help Print usage
|
||||
-l, --label value Service labels (default [])
|
||||
--limit-cpu value Limit CPUs (default 0.000)
|
||||
|
@ -37,6 +41,7 @@ Options:
|
|||
--mount value Attach a mount to the service
|
||||
--name string Service name
|
||||
--network value Network attachments (default [])
|
||||
--no-healthcheck Disable any container-specified HEALTHCHECK
|
||||
-p, --publish value Publish a port as a node port (default [])
|
||||
--replicas value Number of tasks (default none)
|
||||
--reserve-cpu value Reserve CPUs (default 0.000)
|
||||
|
|
|
@ -32,6 +32,10 @@ Options:
|
|||
--force Force update even if no changes require it
|
||||
--group-add value Add additional user groups to the container (default [])
|
||||
--group-rm value Remove previously added user groups from the container (default [])
|
||||
--health-cmd string Command to run to check health
|
||||
--health-interval duration Time between running the check
|
||||
--health-retries int Consecutive failures needed to report unhealthy
|
||||
--health-timeout duration Maximum time to allow one check to run
|
||||
--help Print usage
|
||||
--image string Service image tag
|
||||
--label-add value Add or update service labels (default [])
|
||||
|
@ -42,6 +46,7 @@ Options:
|
|||
--log-opt value Logging driver options (default [])
|
||||
--mount-add value Add or update a mount on a service
|
||||
--mount-rm value Remove a mount by its target path (default [])
|
||||
--no-healthcheck Disable any container-specified HEALTHCHECK
|
||||
--publish-add value Add or update a published port (default [])
|
||||
--publish-rm value Remove a published port by its target port (default [])
|
||||
--replicas value Number of tasks (default none)
|
||||
|
|
|
@ -147,7 +147,7 @@ clone git github.com/docker/containerd 52ef1ceb4b660c42cf4ea9013180a5663968d4c7
|
|||
clone git github.com/tonistiigi/fifo 8c56881ce5e63e19e2dfc495c8af0fb90916467d
|
||||
|
||||
# cluster
|
||||
clone git github.com/docker/swarmkit 0ec7c6ee4b3185ec4e3d6bd65f8f5542b1761421
|
||||
clone git github.com/docker/swarmkit 72981f443024da2c57d54b915eae0477be6dada5
|
||||
clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9
|
||||
clone git github.com/gogo/protobuf v0.3
|
||||
clone git github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
|
||||
|
|
|
@ -54,6 +54,13 @@ type ContainerStatuser interface {
|
|||
ContainerStatus(ctx context.Context) (*api.ContainerStatus, error)
|
||||
}
|
||||
|
||||
// PortStatuser reports status of ports which are allocated by the executor
|
||||
type PortStatuser interface {
|
||||
// PortStatus returns the status on a list of PortConfigs
|
||||
// which are managed at the host level by the controller.
|
||||
PortStatus(ctx context.Context) (*api.PortStatus, error)
|
||||
}
|
||||
|
||||
// Resolve attempts to get a controller from the executor and reports the
|
||||
// correct status depending on the tasks current state according to the result.
|
||||
//
|
||||
|
@ -131,6 +138,7 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus,
|
|||
// this particular method. Eventually, we assemble this as part of a defer.
|
||||
var (
|
||||
containerStatus *api.ContainerStatus
|
||||
portStatus *api.PortStatus
|
||||
exitCode int
|
||||
)
|
||||
|
||||
|
@ -230,6 +238,21 @@ func Do(ctx context.Context, task *api.Task, ctlr Controller) (*api.TaskStatus,
|
|||
status.RuntimeStatus = &api.TaskStatus_Container{
|
||||
Container: containerStatus,
|
||||
}
|
||||
|
||||
if portStatus == nil {
|
||||
pctlr, ok := ctlr.(PortStatuser)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
portStatus, err = pctlr.PortStatus(ctx)
|
||||
if err != nil && !contextDoneError(err) {
|
||||
log.G(ctx).WithError(err).Error("container port status unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
status.PortStatus = portStatus
|
||||
}()
|
||||
|
||||
if task.DesiredState == api.TaskStateShutdown {
|
||||
|
|
|
@ -141,3 +141,35 @@ func (_m *MockContainerStatuser) ContainerStatus(ctx context.Context) (*api.Cont
|
|||
func (_mr *_MockContainerStatuserRecorder) ContainerStatus(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ContainerStatus", arg0)
|
||||
}
|
||||
|
||||
// Mock of PortStatuser interface
|
||||
type MockPortStatuser struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *_MockPortStatuserRecorder
|
||||
}
|
||||
|
||||
// Recorder for MockPortStatuser (not exported)
|
||||
type _MockPortStatuserRecorder struct {
|
||||
mock *MockPortStatuser
|
||||
}
|
||||
|
||||
func NewMockPortStatuser(ctrl *gomock.Controller) *MockPortStatuser {
|
||||
mock := &MockPortStatuser{ctrl: ctrl}
|
||||
mock.recorder = &_MockPortStatuserRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (_m *MockPortStatuser) EXPECT() *_MockPortStatuserRecorder {
|
||||
return _m.recorder
|
||||
}
|
||||
|
||||
func (_m *MockPortStatuser) PortStatus(ctx context.Context) (*api.PortStatus, error) {
|
||||
ret := _m.ctrl.Call(_m, "PortStatus", ctx)
|
||||
ret0, _ := ret[0].(*api.PortStatus)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (_mr *_MockPortStatuserRecorder) PortStatus(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "PortStatus", arg0)
|
||||
}
|
||||
|
|
|
@ -489,6 +489,11 @@ type ContainerSpec struct {
|
|||
Secrets []*SecretReference `protobuf:"bytes,12,rep,name=secrets" json:"secrets,omitempty"`
|
||||
// DNSConfig allows one to specify DNS related configuration in resolv.conf
|
||||
DNSConfig *ContainerSpec_DNSConfig `protobuf:"bytes,15,opt,name=dns_config,json=dnsConfig" json:"dns_config,omitempty"`
|
||||
// Healthcheck describes how to check the container is healthy. If the
|
||||
// container is considered unhealthy, it will be destroyed, its creating
|
||||
// task will exit and a new task will be rescheduled elsewhere. A container
|
||||
// is considered unhealthy after `Retries` number of consecutive failures.
|
||||
Healthcheck *HealthConfig `protobuf:"bytes,16,opt,name=healthcheck" json:"healthcheck,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ContainerSpec) Reset() { *m = ContainerSpec{} }
|
||||
|
@ -756,6 +761,7 @@ func (m *ContainerSpec) Copy() *ContainerSpec {
|
|||
StopGracePeriod: m.StopGracePeriod.Copy(),
|
||||
PullOptions: m.PullOptions.Copy(),
|
||||
DNSConfig: m.DNSConfig.Copy(),
|
||||
Healthcheck: m.Healthcheck.Copy(),
|
||||
}
|
||||
|
||||
if m.Labels != nil {
|
||||
|
@ -1035,7 +1041,7 @@ func (this *ContainerSpec) GoString() string {
|
|||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := make([]string, 0, 19)
|
||||
s := make([]string, 0, 20)
|
||||
s = append(s, "&api.ContainerSpec{")
|
||||
s = append(s, "Image: "+fmt.Sprintf("%#v", this.Image)+",\n")
|
||||
keysForLabels := make([]string, 0, len(this.Labels))
|
||||
|
@ -1074,6 +1080,9 @@ func (this *ContainerSpec) GoString() string {
|
|||
if this.DNSConfig != nil {
|
||||
s = append(s, "DNSConfig: "+fmt.Sprintf("%#v", this.DNSConfig)+",\n")
|
||||
}
|
||||
if this.Healthcheck != nil {
|
||||
s = append(s, "Healthcheck: "+fmt.Sprintf("%#v", this.Healthcheck)+",\n")
|
||||
}
|
||||
s = append(s, "}")
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
|
@ -1681,6 +1690,18 @@ func (m *ContainerSpec) MarshalTo(data []byte) (int, error) {
|
|||
}
|
||||
i += n18
|
||||
}
|
||||
if m.Healthcheck != nil {
|
||||
data[i] = 0x82
|
||||
i++
|
||||
data[i] = 0x1
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Healthcheck.Size()))
|
||||
n19, err := m.Healthcheck.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n19
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
|
@ -1826,20 +1847,20 @@ func (m *NetworkSpec) MarshalTo(data []byte) (int, error) {
|
|||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Annotations.Size()))
|
||||
n19, err := m.Annotations.MarshalTo(data[i:])
|
||||
n20, err := m.Annotations.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n19
|
||||
i += n20
|
||||
if m.DriverConfig != nil {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.DriverConfig.Size()))
|
||||
n20, err := m.DriverConfig.MarshalTo(data[i:])
|
||||
n21, err := m.DriverConfig.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n20
|
||||
i += n21
|
||||
}
|
||||
if m.Ipv6Enabled {
|
||||
data[i] = 0x18
|
||||
|
@ -1865,11 +1886,11 @@ func (m *NetworkSpec) MarshalTo(data []byte) (int, error) {
|
|||
data[i] = 0x2a
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.IPAM.Size()))
|
||||
n21, err := m.IPAM.MarshalTo(data[i:])
|
||||
n22, err := m.IPAM.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n21
|
||||
i += n22
|
||||
}
|
||||
if m.Attachable {
|
||||
data[i] = 0x30
|
||||
|
@ -1902,59 +1923,59 @@ func (m *ClusterSpec) MarshalTo(data []byte) (int, error) {
|
|||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Annotations.Size()))
|
||||
n22, err := m.Annotations.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n22
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.AcceptancePolicy.Size()))
|
||||
n23, err := m.AcceptancePolicy.MarshalTo(data[i:])
|
||||
n23, err := m.Annotations.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n23
|
||||
data[i] = 0x1a
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Orchestration.Size()))
|
||||
n24, err := m.Orchestration.MarshalTo(data[i:])
|
||||
i = encodeVarintSpecs(data, i, uint64(m.AcceptancePolicy.Size()))
|
||||
n24, err := m.AcceptancePolicy.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n24
|
||||
data[i] = 0x22
|
||||
data[i] = 0x1a
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Raft.Size()))
|
||||
n25, err := m.Raft.MarshalTo(data[i:])
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Orchestration.Size()))
|
||||
n25, err := m.Orchestration.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n25
|
||||
data[i] = 0x2a
|
||||
data[i] = 0x22
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Dispatcher.Size()))
|
||||
n26, err := m.Dispatcher.MarshalTo(data[i:])
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Raft.Size()))
|
||||
n26, err := m.Raft.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n26
|
||||
data[i] = 0x32
|
||||
data[i] = 0x2a
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.CAConfig.Size()))
|
||||
n27, err := m.CAConfig.MarshalTo(data[i:])
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Dispatcher.Size()))
|
||||
n27, err := m.Dispatcher.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n27
|
||||
data[i] = 0x3a
|
||||
data[i] = 0x32
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.TaskDefaults.Size()))
|
||||
n28, err := m.TaskDefaults.MarshalTo(data[i:])
|
||||
i = encodeVarintSpecs(data, i, uint64(m.CAConfig.Size()))
|
||||
n28, err := m.CAConfig.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n28
|
||||
data[i] = 0x3a
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.TaskDefaults.Size()))
|
||||
n29, err := m.TaskDefaults.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n29
|
||||
return i, nil
|
||||
}
|
||||
|
||||
|
@ -1976,11 +1997,11 @@ func (m *SecretSpec) MarshalTo(data []byte) (int, error) {
|
|||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintSpecs(data, i, uint64(m.Annotations.Size()))
|
||||
n29, err := m.Annotations.MarshalTo(data[i:])
|
||||
n30, err := m.Annotations.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n29
|
||||
i += n30
|
||||
if len(m.Data) > 0 {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
|
@ -2235,6 +2256,10 @@ func (m *ContainerSpec) Size() (n int) {
|
|||
l = m.DNSConfig.Size()
|
||||
n += 1 + l + sovSpecs(uint64(l))
|
||||
}
|
||||
if m.Healthcheck != nil {
|
||||
l = m.Healthcheck.Size()
|
||||
n += 2 + l + sovSpecs(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -2500,6 +2525,7 @@ func (this *ContainerSpec) String() string {
|
|||
`TTY:` + fmt.Sprintf("%v", this.TTY) + `,`,
|
||||
`Hostname:` + fmt.Sprintf("%v", this.Hostname) + `,`,
|
||||
`DNSConfig:` + strings.Replace(fmt.Sprintf("%v", this.DNSConfig), "ContainerSpec_DNSConfig", "ContainerSpec_DNSConfig", 1) + `,`,
|
||||
`Healthcheck:` + strings.Replace(fmt.Sprintf("%v", this.Healthcheck), "HealthConfig", "HealthConfig", 1) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
|
@ -4047,6 +4073,39 @@ func (m *ContainerSpec) Unmarshal(data []byte) error {
|
|||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 16:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Healthcheck", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowSpecs
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthSpecs
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Healthcheck == nil {
|
||||
m.Healthcheck = &HealthConfig{}
|
||||
}
|
||||
if err := m.Healthcheck.Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipSpecs(data[iNdEx:])
|
||||
|
@ -5069,103 +5128,105 @@ var (
|
|||
func init() { proto.RegisterFile("specs.proto", fileDescriptorSpecs) }
|
||||
|
||||
var fileDescriptorSpecs = []byte{
|
||||
// 1563 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0xcd, 0x6e, 0x23, 0xc7,
|
||||
0x11, 0xe6, 0x88, 0x14, 0x7f, 0x6a, 0xc8, 0x5d, 0x6e, 0xc3, 0x3f, 0xb3, 0xb4, 0x43, 0x72, 0xe9,
|
||||
0x8d, 0x23, 0xc7, 0x88, 0x36, 0x61, 0x02, 0x67, 0x9d, 0x8d, 0x91, 0xf0, 0x2f, 0x5a, 0x46, 0x91,
|
||||
0x4c, 0xb4, 0xe4, 0x05, 0xf6, 0x44, 0xb4, 0x66, 0x5a, 0xe4, 0x40, 0xc3, 0xe9, 0x49, 0x4f, 0x0f,
|
||||
0x0d, 0xdd, 0x72, 0x34, 0xf6, 0x90, 0x37, 0xd0, 0x29, 0x40, 0xde, 0x20, 0xef, 0xb0, 0xc7, 0x1c,
|
||||
0x73, 0x12, 0x2c, 0x3e, 0x41, 0x80, 0xbc, 0x40, 0xd0, 0x3d, 0x3d, 0xe4, 0x30, 0x1e, 0x59, 0x06,
|
||||
0xa2, 0x5b, 0x77, 0xcd, 0xf7, 0x55, 0x77, 0x57, 0x7d, 0xac, 0x2a, 0x82, 0x19, 0x06, 0xd4, 0x0e,
|
||||
// 1586 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0xe3, 0xc6,
|
||||
0x15, 0x16, 0x2d, 0x59, 0x3f, 0x87, 0xd2, 0xae, 0x76, 0x90, 0x1f, 0xae, 0x92, 0x4a, 0x5a, 0x65,
|
||||
0x9b, 0x3a, 0x0d, 0xea, 0x6d, 0xd5, 0x22, 0xdd, 0x74, 0x1b, 0xb4, 0xfa, 0xab, 0x57, 0x75, 0xed,
|
||||
0x08, 0x63, 0x67, 0x81, 0xbd, 0x12, 0xc6, 0xe4, 0x58, 0x22, 0x4c, 0x71, 0xd8, 0xe1, 0x50, 0x81,
|
||||
0xef, 0x7a, 0x19, 0xec, 0x45, 0xdf, 0xc0, 0x57, 0x2d, 0xfa, 0x06, 0x7d, 0x87, 0xbd, 0xec, 0x65,
|
||||
0xaf, 0x8c, 0x5a, 0x4f, 0x50, 0xa0, 0x2f, 0x50, 0xcc, 0x70, 0x28, 0x51, 0x0d, 0x1d, 0x07, 0xa8,
|
||||
0xef, 0x66, 0x0e, 0xbf, 0xef, 0x70, 0xe6, 0x9c, 0x8f, 0xe7, 0x1c, 0x82, 0x19, 0x06, 0xd4, 0x0e,
|
||||
0xf7, 0x03, 0xce, 0x04, 0x43, 0xc8, 0x61, 0xf6, 0x05, 0xe5, 0xfb, 0xe1, 0xd7, 0x84, 0x2f, 0x2e,
|
||||
0x5c, 0xb1, 0xbf, 0xfc, 0x45, 0xc3, 0x14, 0x97, 0x01, 0xd5, 0x80, 0xc6, 0x3b, 0x33, 0x36, 0x63,
|
||||
0x5c, 0xb1, 0xbf, 0xfc, 0x59, 0xc3, 0x14, 0x97, 0x01, 0xd5, 0x80, 0xc6, 0x3b, 0x33, 0x36, 0x63,
|
||||
0x6a, 0xf9, 0x4c, 0xae, 0xb4, 0xf5, 0x7d, 0x27, 0xe2, 0x44, 0xb8, 0xcc, 0x7f, 0x96, 0x2c, 0xe2,
|
||||
0x0f, 0x9d, 0xbf, 0x16, 0xa0, 0x7c, 0xcc, 0x1c, 0x7a, 0x12, 0x50, 0x1b, 0x1d, 0x80, 0x49, 0x7c,
|
||||
0x9f, 0x09, 0x05, 0x08, 0x2d, 0xa3, 0x6d, 0xec, 0x99, 0xdd, 0xd6, 0xfe, 0x77, 0x8f, 0xdc, 0xef,
|
||||
0x6d, 0x60, 0xfd, 0xc2, 0xdb, 0xeb, 0x56, 0x0e, 0xa7, 0x99, 0xe8, 0xe7, 0x50, 0xe0, 0xcc, 0xa3,
|
||||
0xd6, 0x4e, 0xdb, 0xd8, 0x7b, 0xd0, 0xfd, 0x30, 0xcb, 0x83, 0x3c, 0x14, 0x33, 0x8f, 0x62, 0x85,
|
||||
0x44, 0x07, 0x00, 0x0b, 0xba, 0x38, 0xa3, 0x3c, 0x9c, 0xbb, 0x81, 0x95, 0x57, 0xbc, 0x9f, 0xdc,
|
||||
0xc6, 0x93, 0x97, 0xdd, 0x3f, 0x5a, 0xc3, 0x71, 0x8a, 0x8a, 0x8e, 0xa0, 0x4a, 0x96, 0xc4, 0xf5,
|
||||
0xc8, 0x99, 0xeb, 0xb9, 0xe2, 0xd2, 0x2a, 0x28, 0x57, 0x9f, 0x7c, 0xaf, 0xab, 0x5e, 0x8a, 0x80,
|
||||
0xb7, 0xe8, 0x1d, 0x07, 0x60, 0x73, 0x10, 0xfa, 0x18, 0x4a, 0x93, 0xd1, 0xf1, 0x70, 0x7c, 0x7c,
|
||||
0x50, 0xcf, 0x35, 0x1e, 0xbf, 0xb9, 0x6a, 0xbf, 0x2b, 0x7d, 0x6c, 0x00, 0x13, 0xea, 0x3b, 0xae,
|
||||
0x3f, 0x43, 0x7b, 0x50, 0xee, 0x0d, 0x06, 0xa3, 0xc9, 0xe9, 0x68, 0x58, 0x37, 0x1a, 0x8d, 0x37,
|
||||
0x57, 0xed, 0xf7, 0xb6, 0x81, 0x3d, 0xdb, 0xa6, 0x81, 0xa0, 0x4e, 0xa3, 0xf0, 0xcd, 0xdf, 0x9a,
|
||||
0xb9, 0xce, 0x37, 0x06, 0x54, 0xd3, 0x97, 0x40, 0x1f, 0x43, 0xb1, 0x37, 0x38, 0x1d, 0xbf, 0x1a,
|
||||
0xd5, 0x73, 0x1b, 0x7a, 0x1a, 0xd1, 0xb3, 0x85, 0xbb, 0xa4, 0xe8, 0x29, 0xec, 0x4e, 0x7a, 0x5f,
|
||||
0x9d, 0x8c, 0xea, 0xc6, 0xe6, 0x3a, 0x69, 0xd8, 0x84, 0x44, 0xa1, 0x42, 0x0d, 0x71, 0x6f, 0x7c,
|
||||
0x5c, 0xdf, 0xc9, 0x46, 0x0d, 0x39, 0x71, 0x7d, 0x7d, 0x95, 0x9b, 0x3c, 0x98, 0x27, 0x94, 0x2f,
|
||||
0x5d, 0xfb, 0x9e, 0x35, 0xf1, 0x19, 0x14, 0x04, 0x09, 0x2f, 0x94, 0x26, 0xcc, 0x6c, 0x4d, 0x9c,
|
||||
0x92, 0xf0, 0x42, 0x1e, 0xaa, 0xe9, 0x0a, 0x2f, 0x95, 0xc1, 0x69, 0xe0, 0xb9, 0x36, 0x11, 0xd4,
|
||||
0x51, 0xca, 0x30, 0xbb, 0x3f, 0xce, 0x62, 0xe3, 0x35, 0x4a, 0xdf, 0xff, 0x65, 0x0e, 0xa7, 0xa8,
|
||||
0xe8, 0x05, 0x14, 0x67, 0x1e, 0x3b, 0x23, 0x9e, 0xd2, 0x84, 0xd9, 0x7d, 0x92, 0xe5, 0xe4, 0x40,
|
||||
0x21, 0x36, 0x0e, 0x34, 0x05, 0x3d, 0x87, 0x62, 0x14, 0x38, 0x44, 0x50, 0xab, 0xa8, 0xc8, 0xed,
|
||||
0x2c, 0xf2, 0x57, 0x0a, 0x31, 0x60, 0xfe, 0xb9, 0x3b, 0xc3, 0x1a, 0x8f, 0x0e, 0xa1, 0xec, 0x53,
|
||||
0xf1, 0x35, 0xe3, 0x17, 0xa1, 0x55, 0x6a, 0xe7, 0xf7, 0xcc, 0xee, 0xa7, 0x99, 0x62, 0x8c, 0x31,
|
||||
0x3d, 0x21, 0x88, 0x3d, 0x5f, 0x50, 0x5f, 0xc4, 0x6e, 0xfa, 0x3b, 0x96, 0x81, 0xd7, 0x0e, 0xd0,
|
||||
0x6f, 0xa1, 0x4c, 0x7d, 0x27, 0x60, 0xae, 0x2f, 0xac, 0xf2, 0xed, 0x17, 0x19, 0x69, 0x8c, 0x0c,
|
||||
0x26, 0x5e, 0x33, 0xfa, 0x45, 0x28, 0x2c, 0x98, 0x43, 0x3b, 0xcf, 0xe0, 0xd1, 0x77, 0x82, 0x85,
|
||||
0x1a, 0x50, 0xd6, 0xc1, 0x8a, 0xb3, 0x5c, 0xc0, 0xeb, 0x7d, 0xe7, 0x21, 0xd4, 0xb6, 0x02, 0xa3,
|
||||
0xca, 0x46, 0x92, 0x2d, 0xd4, 0x83, 0x8a, 0xcd, 0x7c, 0x41, 0x5c, 0x9f, 0x72, 0x2d, 0x90, 0xcc,
|
||||
0xd8, 0x0e, 0x12, 0x90, 0x64, 0xbd, 0xcc, 0xe1, 0x0d, 0x0b, 0xfd, 0x01, 0x2a, 0x9c, 0x86, 0x2c,
|
||||
0xe2, 0x36, 0x0d, 0xb5, 0x42, 0xf6, 0xb2, 0x73, 0x1c, 0x83, 0x30, 0xfd, 0x73, 0xe4, 0x72, 0x2a,
|
||||
0xe3, 0x14, 0xe2, 0x0d, 0x15, 0xbd, 0x80, 0x12, 0xa7, 0xa1, 0x20, 0x5c, 0x7c, 0x5f, 0x92, 0x71,
|
||||
0x0c, 0x99, 0x30, 0xcf, 0xb5, 0x2f, 0x71, 0xc2, 0x40, 0x2f, 0xa0, 0x12, 0x78, 0xc4, 0x56, 0x5e,
|
||||
0xad, 0x5d, 0x45, 0xff, 0x51, 0x16, 0x7d, 0x92, 0x80, 0xf0, 0x06, 0x8f, 0x3e, 0x07, 0xf0, 0xd8,
|
||||
0x6c, 0xea, 0x70, 0x77, 0x49, 0xb9, 0x16, 0x49, 0x23, 0x8b, 0x3d, 0x54, 0x08, 0x5c, 0xf1, 0xd8,
|
||||
0x2c, 0x5e, 0xa2, 0x83, 0xff, 0x4b, 0x21, 0x29, 0x75, 0x1c, 0x02, 0x90, 0xf5, 0x57, 0xad, 0x8f,
|
||||
0x4f, 0x7e, 0x90, 0x2b, 0x9d, 0x91, 0x14, 0x1d, 0x3d, 0x81, 0xea, 0x39, 0xe3, 0x36, 0x9d, 0x6a,
|
||||
0xdd, 0x57, 0x94, 0x26, 0x4c, 0x65, 0x8b, 0x85, 0xde, 0xaf, 0x40, 0x89, 0x47, 0xbe, 0x70, 0x17,
|
||||
0xb4, 0x73, 0x08, 0xef, 0x66, 0x3a, 0x45, 0x5d, 0xa8, 0xae, 0xd3, 0x3c, 0x75, 0x1d, 0xa5, 0x8f,
|
||||
0x4a, 0xff, 0xe1, 0xea, 0xba, 0x65, 0xae, 0xf5, 0x30, 0x1e, 0x62, 0x73, 0x0d, 0x1a, 0x3b, 0x9d,
|
||||
0x6f, 0x8b, 0x50, 0xdb, 0x12, 0x0b, 0x7a, 0x07, 0x76, 0xdd, 0x05, 0x99, 0xd1, 0x98, 0x8e, 0xe3,
|
||||
0x0d, 0x1a, 0x41, 0xd1, 0x23, 0x67, 0xd4, 0x93, 0x92, 0x91, 0x61, 0xfb, 0xd9, 0x9d, 0xaa, 0xdb,
|
||||
0xff, 0x93, 0xc2, 0x8f, 0x7c, 0xc1, 0x2f, 0xb1, 0x26, 0x23, 0x0b, 0x4a, 0x36, 0x5b, 0x2c, 0x88,
|
||||
0x2f, 0xcb, 0x4b, 0x7e, 0xaf, 0x82, 0x93, 0x2d, 0x42, 0x50, 0x20, 0x7c, 0x16, 0x5a, 0x05, 0x65,
|
||||
0x56, 0x6b, 0x54, 0x87, 0x3c, 0xf5, 0x97, 0xd6, 0xae, 0x32, 0xc9, 0xa5, 0xb4, 0x38, 0x6e, 0x9c,
|
||||
0xf3, 0x0a, 0x96, 0x4b, 0xc9, 0x8b, 0x42, 0xca, 0xad, 0x92, 0x32, 0xa9, 0x35, 0xfa, 0x35, 0x14,
|
||||
0x17, 0x2c, 0xf2, 0x45, 0x68, 0x95, 0xd5, 0x65, 0x1f, 0x67, 0x5d, 0xf6, 0x48, 0x22, 0x74, 0xf9,
|
||||
0xd3, 0x70, 0xf4, 0x12, 0x1e, 0x85, 0x82, 0x05, 0xd3, 0x19, 0x27, 0x36, 0x9d, 0x06, 0x94, 0xbb,
|
||||
0xcc, 0x51, 0xd9, 0xb8, 0xa5, 0x8a, 0x0e, 0x75, 0x87, 0xc7, 0x0f, 0x25, 0xed, 0x40, 0xb2, 0x26,
|
||||
0x8a, 0x84, 0x26, 0x50, 0x0d, 0x22, 0xcf, 0x9b, 0xb2, 0x20, 0x2e, 0xe6, 0xa0, 0x9c, 0xfc, 0x80,
|
||||
0xa8, 0x4d, 0x22, 0xcf, 0xfb, 0x32, 0x26, 0x61, 0x33, 0xd8, 0x6c, 0xd0, 0x7b, 0x50, 0x9c, 0x71,
|
||||
0x16, 0x05, 0xa1, 0x65, 0xaa, 0x78, 0xe8, 0x1d, 0xfa, 0x02, 0x4a, 0x21, 0xb5, 0x39, 0x15, 0xa1,
|
||||
0x55, 0x55, 0xaf, 0xfd, 0x28, 0xeb, 0x90, 0x13, 0x05, 0xc1, 0xf4, 0x9c, 0x72, 0xea, 0xdb, 0x14,
|
||||
0x27, 0x1c, 0xf4, 0x18, 0xf2, 0x42, 0x5c, 0x5a, 0xb5, 0xb6, 0xb1, 0x57, 0xee, 0x97, 0x56, 0xd7,
|
||||
0xad, 0xfc, 0xe9, 0xe9, 0x6b, 0x2c, 0x6d, 0xb2, 0x4c, 0xcd, 0x59, 0x28, 0x7c, 0xb2, 0xa0, 0xd6,
|
||||
0x03, 0x15, 0xde, 0xf5, 0x1e, 0xbd, 0x06, 0x70, 0xfc, 0x70, 0x6a, 0xab, 0xdf, 0x85, 0xf5, 0x50,
|
||||
0xbd, 0xee, 0xd3, 0xbb, 0x5f, 0x37, 0x3c, 0x3e, 0xd1, 0xc5, 0xb6, 0xb6, 0xba, 0x6e, 0x55, 0xd6,
|
||||
0x5b, 0x5c, 0x71, 0xfc, 0x30, 0x5e, 0x36, 0x3e, 0x07, 0x33, 0x25, 0x1d, 0x99, 0xf2, 0x0b, 0x7a,
|
||||
0xa9, 0xd5, 0x28, 0x97, 0x52, 0xa1, 0x4b, 0xe2, 0x45, 0xf1, 0xcc, 0x53, 0xc1, 0xf1, 0xe6, 0x37,
|
||||
0x3b, 0xcf, 0x8d, 0x46, 0x17, 0xcc, 0x54, 0xfc, 0xd0, 0x47, 0x50, 0xe3, 0x74, 0xe6, 0x86, 0x82,
|
||||
0x5f, 0x4e, 0x49, 0x24, 0xe6, 0xd6, 0xef, 0x15, 0xa1, 0x9a, 0x18, 0x7b, 0x91, 0x98, 0x37, 0xa6,
|
||||
0xb0, 0xb9, 0x06, 0x6a, 0x83, 0x29, 0x9f, 0x17, 0x52, 0xbe, 0xa4, 0x5c, 0x16, 0x67, 0x19, 0xe9,
|
||||
0xb4, 0x49, 0xa6, 0x21, 0xa4, 0x84, 0xdb, 0x73, 0xf5, 0x43, 0xa8, 0x60, 0xbd, 0x93, 0xca, 0x4e,
|
||||
0x72, 0xad, 0x95, 0xad, 0xb7, 0x9d, 0xff, 0x18, 0x50, 0x4d, 0x77, 0x09, 0x34, 0x88, 0x7b, 0x83,
|
||||
0x7a, 0xd2, 0x83, 0xee, 0xb3, 0xbb, 0xba, 0x8a, 0xaa, 0xc4, 0x5e, 0x24, 0x9d, 0x1d, 0xc9, 0x49,
|
||||
0x4e, 0x91, 0xd1, 0xaf, 0x60, 0x37, 0x60, 0x5c, 0x24, 0xbf, 0xc7, 0x66, 0x66, 0xf5, 0x64, 0x3c,
|
||||
0xa9, 0x5c, 0x31, 0xb8, 0x33, 0x87, 0x07, 0xdb, 0xde, 0xd0, 0x53, 0xc8, 0xbf, 0x1a, 0x4f, 0xea,
|
||||
0xb9, 0xc6, 0x07, 0x6f, 0xae, 0xda, 0xef, 0x6f, 0x7f, 0x7c, 0xe5, 0x72, 0x11, 0x11, 0x6f, 0x3c,
|
||||
0x41, 0x3f, 0x85, 0xdd, 0xe1, 0xf1, 0x09, 0xc6, 0x75, 0xa3, 0xd1, 0x7a, 0x73, 0xd5, 0xfe, 0x60,
|
||||
0x1b, 0x27, 0x3f, 0xb1, 0xc8, 0x77, 0x30, 0x3b, 0x5b, 0x0f, 0x37, 0xff, 0xd8, 0x01, 0x53, 0x97,
|
||||
0xa9, 0xfb, 0x1d, 0x6e, 0x7e, 0x07, 0xb5, 0xb8, 0xf2, 0x27, 0xe2, 0xdb, 0xb9, 0xb3, 0x01, 0x54,
|
||||
0x63, 0x82, 0xce, 0xf1, 0x13, 0xa8, 0xba, 0xc1, 0xf2, 0xb3, 0x29, 0xf5, 0xc9, 0x99, 0xa7, 0xe7,
|
||||
0x9c, 0x32, 0x36, 0xa5, 0x6d, 0x14, 0x9b, 0xa4, 0xf2, 0x5d, 0x5f, 0x50, 0xee, 0xeb, 0x09, 0xa6,
|
||||
0x8c, 0xd7, 0x7b, 0xf4, 0x05, 0x14, 0xdc, 0x80, 0x2c, 0x74, 0xd7, 0xca, 0x7c, 0xc1, 0x78, 0xd2,
|
||||
0x3b, 0xd2, 0x1a, 0xec, 0x97, 0x57, 0xd7, 0xad, 0x82, 0x34, 0x60, 0x45, 0x43, 0xcd, 0xa4, 0x71,
|
||||
0xc8, 0x93, 0x54, 0x21, 0x2b, 0xe3, 0x94, 0xa5, 0xf3, 0xf7, 0x02, 0x98, 0x03, 0x2f, 0x0a, 0x85,
|
||||
0x2e, 0xc7, 0xf7, 0x16, 0xb7, 0xd7, 0xf0, 0x88, 0xa8, 0x51, 0x98, 0xf8, 0xb2, 0xb6, 0xa9, 0x86,
|
||||
0xac, 0x63, 0xf7, 0x34, 0xd3, 0xdd, 0x1a, 0x1c, 0x37, 0xef, 0x7e, 0x51, 0xfa, 0xb4, 0x0c, 0x5c,
|
||||
0x27, 0xff, 0xf3, 0x05, 0x9d, 0x40, 0x8d, 0x71, 0x7b, 0x4e, 0x43, 0x11, 0x97, 0x43, 0x3d, 0x3a,
|
||||
0x66, 0xfe, 0xa9, 0xf8, 0x32, 0x0d, 0xd4, 0xb5, 0x20, 0xbe, 0xed, 0xb6, 0x0f, 0xf4, 0x1c, 0x0a,
|
||||
0x9c, 0x9c, 0x27, 0xc3, 0x45, 0xa6, 0xbe, 0x31, 0x39, 0x17, 0x5b, 0x2e, 0x14, 0x03, 0xfd, 0x11,
|
||||
0xc0, 0x71, 0xc3, 0x80, 0x08, 0x7b, 0x4e, 0xb9, 0xce, 0x53, 0xe6, 0x13, 0x87, 0x6b, 0xd4, 0x96,
|
||||
0x97, 0x14, 0x1b, 0x1d, 0x42, 0xc5, 0x26, 0x89, 0xd2, 0x8a, 0xb7, 0x77, 0x82, 0x41, 0x4f, 0xbb,
|
||||
0xa8, 0x4b, 0x17, 0xab, 0xeb, 0x56, 0x39, 0xb1, 0xe0, 0xb2, 0x4d, 0xb4, 0xf2, 0x0e, 0xa1, 0x26,
|
||||
0xe7, 0xec, 0xa9, 0x43, 0xcf, 0x49, 0xe4, 0x89, 0x50, 0x35, 0xad, 0x5b, 0xe6, 0x4a, 0x39, 0xf2,
|
||||
0x0d, 0x35, 0x4e, 0xdf, 0xab, 0x2a, 0x52, 0xb6, 0x8e, 0x0b, 0x10, 0x17, 0xf5, 0xfb, 0x95, 0x09,
|
||||
0x82, 0x82, 0x43, 0x04, 0x51, 0xca, 0xa8, 0x62, 0xb5, 0xee, 0x7f, 0xf8, 0xf6, 0xa6, 0x99, 0xfb,
|
||||
0xd7, 0x4d, 0x33, 0xf7, 0xef, 0x9b, 0xa6, 0xf1, 0x97, 0x55, 0xd3, 0x78, 0xbb, 0x6a, 0x1a, 0xff,
|
||||
0x5c, 0x35, 0x8d, 0x6f, 0x57, 0x4d, 0xe3, 0xac, 0xa8, 0xfe, 0xde, 0xfe, 0xf2, 0xbf, 0x01, 0x00,
|
||||
0x00, 0xff, 0xff, 0x2a, 0x90, 0x7c, 0x40, 0x3d, 0x0f, 0x00, 0x00,
|
||||
0x07, 0x9d, 0x3f, 0x17, 0xa0, 0x7c, 0xcc, 0x1c, 0x7a, 0x12, 0x50, 0x1b, 0x1d, 0x80, 0x49, 0x7c,
|
||||
0x9f, 0x09, 0x05, 0x08, 0x2d, 0xa3, 0x6d, 0xec, 0x99, 0xdd, 0xd6, 0xfe, 0xb7, 0x5f, 0xb9, 0xdf,
|
||||
0xdb, 0xc0, 0xfa, 0x85, 0xb7, 0xd7, 0xad, 0x1c, 0x4e, 0x33, 0xd1, 0x4f, 0xa1, 0xc0, 0x99, 0x47,
|
||||
0xad, 0x9d, 0xb6, 0xb1, 0xf7, 0xa0, 0xfb, 0x61, 0x96, 0x07, 0xf9, 0x52, 0xcc, 0x3c, 0x8a, 0x15,
|
||||
0x12, 0x1d, 0x00, 0x2c, 0xe8, 0xe2, 0x8c, 0xf2, 0x70, 0xee, 0x06, 0x56, 0x5e, 0xf1, 0x7e, 0x74,
|
||||
0x1b, 0x4f, 0x1e, 0x76, 0xff, 0x68, 0x0d, 0xc7, 0x29, 0x2a, 0x3a, 0x82, 0x2a, 0x59, 0x12, 0xd7,
|
||||
0x23, 0x67, 0xae, 0xe7, 0x8a, 0x4b, 0xab, 0xa0, 0x5c, 0x7d, 0xf2, 0x9d, 0xae, 0x7a, 0x29, 0x02,
|
||||
0xde, 0xa2, 0x77, 0x1c, 0x80, 0xcd, 0x8b, 0xd0, 0xc7, 0x50, 0x9a, 0x8c, 0x8e, 0x87, 0xe3, 0xe3,
|
||||
0x83, 0x7a, 0xae, 0xf1, 0xf8, 0xcd, 0x55, 0xfb, 0x5d, 0xe9, 0x63, 0x03, 0x98, 0x50, 0xdf, 0x71,
|
||||
0xfd, 0x19, 0xda, 0x83, 0x72, 0x6f, 0x30, 0x18, 0x4d, 0x4e, 0x47, 0xc3, 0xba, 0xd1, 0x68, 0xbc,
|
||||
0xb9, 0x6a, 0xbf, 0xb7, 0x0d, 0xec, 0xd9, 0x36, 0x0d, 0x04, 0x75, 0x1a, 0x85, 0x6f, 0xfe, 0xd2,
|
||||
0xcc, 0x75, 0xbe, 0x31, 0xa0, 0x9a, 0x3e, 0x04, 0xfa, 0x18, 0x8a, 0xbd, 0xc1, 0xe9, 0xf8, 0xd5,
|
||||
0xa8, 0x9e, 0xdb, 0xd0, 0xd3, 0x88, 0x9e, 0x2d, 0xdc, 0x25, 0x45, 0x4f, 0x61, 0x77, 0xd2, 0xfb,
|
||||
0xea, 0x64, 0x54, 0x37, 0x36, 0xc7, 0x49, 0xc3, 0x26, 0x24, 0x0a, 0x15, 0x6a, 0x88, 0x7b, 0xe3,
|
||||
0xe3, 0xfa, 0x4e, 0x36, 0x6a, 0xc8, 0x89, 0xeb, 0xeb, 0xa3, 0xdc, 0xe4, 0xc1, 0x3c, 0xa1, 0x7c,
|
||||
0xe9, 0xda, 0xf7, 0xac, 0x89, 0xcf, 0xa0, 0x20, 0x48, 0x78, 0xa1, 0x34, 0x61, 0x66, 0x6b, 0xe2,
|
||||
0x94, 0x84, 0x17, 0xf2, 0xa5, 0x9a, 0xae, 0xf0, 0x52, 0x19, 0x9c, 0x06, 0x9e, 0x6b, 0x13, 0x41,
|
||||
0x1d, 0xa5, 0x0c, 0xb3, 0xfb, 0xc3, 0x2c, 0x36, 0x5e, 0xa3, 0xf4, 0xf9, 0x5f, 0xe6, 0x70, 0x8a,
|
||||
0x8a, 0x5e, 0x40, 0x71, 0xe6, 0xb1, 0x33, 0xe2, 0x29, 0x4d, 0x98, 0xdd, 0x27, 0x59, 0x4e, 0x0e,
|
||||
0x14, 0x62, 0xe3, 0x40, 0x53, 0xd0, 0x73, 0x28, 0x46, 0x81, 0x43, 0x04, 0xb5, 0x8a, 0x8a, 0xdc,
|
||||
0xce, 0x22, 0x7f, 0xa5, 0x10, 0x03, 0xe6, 0x9f, 0xbb, 0x33, 0xac, 0xf1, 0xe8, 0x10, 0xca, 0x3e,
|
||||
0x15, 0x5f, 0x33, 0x7e, 0x11, 0x5a, 0xa5, 0x76, 0x7e, 0xcf, 0xec, 0x7e, 0x9a, 0x29, 0xc6, 0x18,
|
||||
0xd3, 0x13, 0x82, 0xd8, 0xf3, 0x05, 0xf5, 0x45, 0xec, 0xa6, 0xbf, 0x63, 0x19, 0x78, 0xed, 0x00,
|
||||
0xfd, 0x1a, 0xca, 0xd4, 0x77, 0x02, 0xe6, 0xfa, 0xc2, 0x2a, 0xdf, 0x7e, 0x90, 0x91, 0xc6, 0xc8,
|
||||
0x60, 0xe2, 0x35, 0xa3, 0x5f, 0x84, 0xc2, 0x82, 0x39, 0xb4, 0xf3, 0x0c, 0x1e, 0x7d, 0x2b, 0x58,
|
||||
0xa8, 0x01, 0x65, 0x1d, 0xac, 0x38, 0xcb, 0x05, 0xbc, 0xde, 0x77, 0x1e, 0x42, 0x6d, 0x2b, 0x30,
|
||||
0xaa, 0x6c, 0x24, 0xd9, 0x42, 0x3d, 0xa8, 0xd8, 0xcc, 0x17, 0xc4, 0xf5, 0x29, 0xd7, 0x02, 0xc9,
|
||||
0x8c, 0xed, 0x20, 0x01, 0x49, 0xd6, 0xcb, 0x1c, 0xde, 0xb0, 0xd0, 0xef, 0xa0, 0xc2, 0x69, 0xc8,
|
||||
0x22, 0x6e, 0xd3, 0x50, 0x2b, 0x64, 0x2f, 0x3b, 0xc7, 0x31, 0x08, 0xd3, 0x3f, 0x46, 0x2e, 0xa7,
|
||||
0x32, 0x4e, 0x21, 0xde, 0x50, 0xd1, 0x0b, 0x28, 0x71, 0x1a, 0x0a, 0xc2, 0xc5, 0x77, 0x25, 0x19,
|
||||
0xc7, 0x90, 0x09, 0xf3, 0x5c, 0xfb, 0x12, 0x27, 0x0c, 0xf4, 0x02, 0x2a, 0x81, 0x47, 0x6c, 0xe5,
|
||||
0xd5, 0xda, 0x55, 0xf4, 0x1f, 0x64, 0xd1, 0x27, 0x09, 0x08, 0x6f, 0xf0, 0xe8, 0x73, 0x00, 0x8f,
|
||||
0xcd, 0xa6, 0x0e, 0x77, 0x97, 0x94, 0x6b, 0x91, 0x34, 0xb2, 0xd8, 0x43, 0x85, 0xc0, 0x15, 0x8f,
|
||||
0xcd, 0xe2, 0x25, 0x3a, 0xf8, 0xbf, 0x14, 0x92, 0x52, 0xc7, 0x21, 0x00, 0x59, 0x3f, 0xd5, 0xfa,
|
||||
0xf8, 0xe4, 0x7b, 0xb9, 0xd2, 0x19, 0x49, 0xd1, 0xd1, 0x13, 0xa8, 0x9e, 0x33, 0x6e, 0xd3, 0xa9,
|
||||
0xd6, 0x7d, 0x45, 0x69, 0xc2, 0x54, 0xb6, 0x58, 0xe8, 0xfd, 0x0a, 0x94, 0x78, 0xe4, 0x0b, 0x77,
|
||||
0x41, 0x3b, 0x87, 0xf0, 0x6e, 0xa6, 0x53, 0xd4, 0x85, 0xea, 0x3a, 0xcd, 0x53, 0xd7, 0x51, 0xfa,
|
||||
0xa8, 0xf4, 0x1f, 0xae, 0xae, 0x5b, 0xe6, 0x5a, 0x0f, 0xe3, 0x21, 0x36, 0xd7, 0xa0, 0xb1, 0xd3,
|
||||
0xf9, 0x6b, 0x09, 0x6a, 0x5b, 0x62, 0x41, 0xef, 0xc0, 0xae, 0xbb, 0x20, 0x33, 0x1a, 0xd3, 0x71,
|
||||
0xbc, 0x41, 0x23, 0x28, 0x7a, 0xe4, 0x8c, 0x7a, 0x52, 0x32, 0x32, 0x6c, 0x3f, 0xb9, 0x53, 0x75,
|
||||
0xfb, 0x7f, 0x50, 0xf8, 0x91, 0x2f, 0xf8, 0x25, 0xd6, 0x64, 0x64, 0x41, 0xc9, 0x66, 0x8b, 0x05,
|
||||
0xf1, 0x65, 0x79, 0xc9, 0xef, 0x55, 0x70, 0xb2, 0x45, 0x08, 0x0a, 0x84, 0xcf, 0x42, 0xab, 0xa0,
|
||||
0xcc, 0x6a, 0x8d, 0xea, 0x90, 0xa7, 0xfe, 0xd2, 0xda, 0x55, 0x26, 0xb9, 0x94, 0x16, 0xc7, 0x8d,
|
||||
0x73, 0x5e, 0xc1, 0x72, 0x29, 0x79, 0x51, 0x48, 0xb9, 0x55, 0x52, 0x26, 0xb5, 0x46, 0xbf, 0x84,
|
||||
0xe2, 0x82, 0x45, 0xbe, 0x08, 0xad, 0xb2, 0x3a, 0xec, 0xe3, 0xac, 0xc3, 0x1e, 0x49, 0x84, 0x2e,
|
||||
0x7f, 0x1a, 0x8e, 0x5e, 0xc2, 0xa3, 0x50, 0xb0, 0x60, 0x3a, 0xe3, 0xc4, 0xa6, 0xd3, 0x80, 0x72,
|
||||
0x97, 0x39, 0x2a, 0x1b, 0xb7, 0x54, 0xd1, 0xa1, 0xee, 0xf0, 0xf8, 0xa1, 0xa4, 0x1d, 0x48, 0xd6,
|
||||
0x44, 0x91, 0xd0, 0x04, 0xaa, 0x41, 0xe4, 0x79, 0x53, 0x16, 0xc4, 0xc5, 0x1c, 0x94, 0x93, 0xef,
|
||||
0x11, 0xb5, 0x49, 0xe4, 0x79, 0x5f, 0xc6, 0x24, 0x6c, 0x06, 0x9b, 0x0d, 0x7a, 0x0f, 0x8a, 0x33,
|
||||
0xce, 0xa2, 0x20, 0xb4, 0x4c, 0x15, 0x0f, 0xbd, 0x43, 0x5f, 0x40, 0x29, 0xa4, 0x36, 0xa7, 0x22,
|
||||
0xb4, 0xaa, 0xea, 0xb6, 0x1f, 0x65, 0xbd, 0xe4, 0x44, 0x41, 0x30, 0x3d, 0xa7, 0x9c, 0xfa, 0x36,
|
||||
0xc5, 0x09, 0x07, 0x3d, 0x86, 0xbc, 0x10, 0x97, 0x56, 0xad, 0x6d, 0xec, 0x95, 0xfb, 0xa5, 0xd5,
|
||||
0x75, 0x2b, 0x7f, 0x7a, 0xfa, 0x1a, 0x4b, 0x9b, 0x2c, 0x53, 0x73, 0x16, 0x0a, 0x9f, 0x2c, 0xa8,
|
||||
0xf5, 0x40, 0x85, 0x77, 0xbd, 0x47, 0xaf, 0x01, 0x1c, 0x3f, 0x9c, 0xda, 0xea, 0xbb, 0xb0, 0x1e,
|
||||
0xaa, 0xdb, 0x7d, 0x7a, 0xf7, 0xed, 0x86, 0xc7, 0x27, 0xba, 0xd8, 0xd6, 0x56, 0xd7, 0xad, 0xca,
|
||||
0x7a, 0x8b, 0x2b, 0x8e, 0x1f, 0xc6, 0x4b, 0xd4, 0x07, 0x73, 0x4e, 0x89, 0x27, 0xe6, 0xf6, 0x9c,
|
||||
0xda, 0x17, 0x56, 0xfd, 0xf6, 0xda, 0xfb, 0x52, 0xc1, 0xb4, 0x87, 0x34, 0xa9, 0xf1, 0x39, 0x98,
|
||||
0x29, 0xf9, 0x49, 0xd9, 0x5c, 0xd0, 0x4b, 0xad, 0x68, 0xb9, 0x94, 0x2a, 0x5f, 0x12, 0x2f, 0x8a,
|
||||
0xe7, 0xa6, 0x0a, 0x8e, 0x37, 0xbf, 0xda, 0x79, 0x6e, 0x34, 0xba, 0x60, 0xa6, 0x72, 0x80, 0x3e,
|
||||
0x82, 0x1a, 0xa7, 0x33, 0x37, 0x14, 0xfc, 0x72, 0x4a, 0x22, 0x31, 0xb7, 0x7e, 0xab, 0x08, 0xd5,
|
||||
0xc4, 0xd8, 0x8b, 0xc4, 0xbc, 0x31, 0x85, 0xcd, 0x55, 0x50, 0x1b, 0x4c, 0x19, 0xa2, 0x90, 0xf2,
|
||||
0x25, 0xe5, 0xb2, 0xc0, 0xcb, 0x6c, 0xa5, 0x4d, 0x32, 0x95, 0x21, 0x25, 0xdc, 0x9e, 0xab, 0x8f,
|
||||
0xa9, 0x82, 0xf5, 0x4e, 0x7e, 0x1d, 0x89, 0x5e, 0xf4, 0xd7, 0xa1, 0xb7, 0x9d, 0xff, 0x18, 0x50,
|
||||
0x4d, 0x77, 0x1a, 0x34, 0x88, 0xfb, 0x8b, 0xba, 0xd2, 0x83, 0xee, 0xb3, 0xbb, 0x3a, 0x93, 0xaa,
|
||||
0xe6, 0x5e, 0x24, 0x9d, 0x1d, 0xc9, 0x69, 0x50, 0x91, 0xd1, 0x2f, 0x60, 0x37, 0x60, 0x5c, 0x24,
|
||||
0xdf, 0x74, 0x33, 0xb3, 0x02, 0x33, 0x9e, 0x54, 0xbf, 0x18, 0xdc, 0x99, 0xc3, 0x83, 0x6d, 0x6f,
|
||||
0xe8, 0x29, 0xe4, 0x5f, 0x8d, 0x27, 0xf5, 0x5c, 0xe3, 0x83, 0x37, 0x57, 0xed, 0xf7, 0xb7, 0x1f,
|
||||
0xbe, 0x72, 0xb9, 0x88, 0x88, 0x37, 0x9e, 0xa0, 0x1f, 0xc3, 0xee, 0xf0, 0xf8, 0x04, 0xe3, 0xba,
|
||||
0xd1, 0x68, 0xbd, 0xb9, 0x6a, 0x7f, 0xb0, 0x8d, 0x93, 0x8f, 0x58, 0xe4, 0x3b, 0x98, 0x9d, 0xad,
|
||||
0x07, 0xa4, 0xbf, 0xef, 0x80, 0xa9, 0x4b, 0xdd, 0xfd, 0x0e, 0x48, 0xbf, 0x81, 0x5a, 0xdc, 0x3d,
|
||||
0x12, 0x01, 0xef, 0xdc, 0xd9, 0x44, 0xaa, 0x31, 0x41, 0xe7, 0xf8, 0x09, 0x54, 0xdd, 0x60, 0xf9,
|
||||
0xd9, 0x94, 0xfa, 0xe4, 0xcc, 0xd3, 0xb3, 0x52, 0x19, 0x9b, 0xd2, 0x36, 0x8a, 0x4d, 0xf2, 0xeb,
|
||||
0x71, 0x7d, 0x41, 0xb9, 0xaf, 0xa7, 0xa0, 0x32, 0x5e, 0xef, 0xd1, 0x17, 0x50, 0x70, 0x03, 0xb2,
|
||||
0xd0, 0x9d, 0x2f, 0xf3, 0x06, 0xe3, 0x49, 0xef, 0x48, 0x6b, 0xb0, 0x5f, 0x5e, 0x5d, 0xb7, 0x0a,
|
||||
0xd2, 0x80, 0x15, 0x0d, 0x35, 0x93, 0xe6, 0x23, 0xdf, 0xa4, 0x8a, 0x61, 0x19, 0xa7, 0x2c, 0x9d,
|
||||
0xbf, 0x15, 0xc0, 0x1c, 0x78, 0x51, 0x28, 0x74, 0x49, 0xbf, 0xb7, 0xb8, 0xbd, 0x86, 0x47, 0x44,
|
||||
0x8d, 0xd3, 0xc4, 0x97, 0xf5, 0x51, 0x35, 0x75, 0x1d, 0xbb, 0xa7, 0x99, 0xee, 0xd6, 0xe0, 0x78,
|
||||
0x00, 0xe8, 0x17, 0xa5, 0x4f, 0xcb, 0xc0, 0x75, 0xf2, 0x3f, 0x4f, 0xd0, 0x09, 0xd4, 0x18, 0xb7,
|
||||
0xe7, 0x34, 0x14, 0x71, 0x49, 0xd5, 0xe3, 0x67, 0xe6, 0x8f, 0xc9, 0x97, 0x69, 0xa0, 0xae, 0x27,
|
||||
0xf1, 0x69, 0xb7, 0x7d, 0xa0, 0xe7, 0x50, 0xe0, 0xe4, 0x3c, 0x19, 0x50, 0x32, 0xf5, 0x8d, 0xc9,
|
||||
0xb9, 0xd8, 0x72, 0xa1, 0x18, 0xe8, 0xf7, 0x00, 0x8e, 0x1b, 0x06, 0x44, 0xd8, 0x73, 0xca, 0x75,
|
||||
0x9e, 0x32, 0xaf, 0x38, 0x5c, 0xa3, 0xb6, 0xbc, 0xa4, 0xd8, 0xe8, 0x10, 0x2a, 0x36, 0x49, 0x94,
|
||||
0x56, 0xbc, 0xbd, 0x9b, 0x0c, 0x7a, 0xda, 0x45, 0x5d, 0xba, 0x58, 0x5d, 0xb7, 0xca, 0x89, 0x05,
|
||||
0x97, 0x6d, 0xa2, 0x95, 0x77, 0x08, 0x35, 0x39, 0xab, 0x4f, 0x1d, 0x7a, 0x4e, 0x22, 0x4f, 0x84,
|
||||
0xaa, 0xf1, 0xdd, 0x52, 0x1f, 0xe5, 0xd8, 0x38, 0xd4, 0x38, 0x7d, 0xae, 0xaa, 0x48, 0xd9, 0x3a,
|
||||
0x2e, 0x40, 0xdc, 0x18, 0xee, 0x57, 0x26, 0x08, 0x0a, 0x0e, 0x11, 0x44, 0x29, 0xa3, 0x8a, 0xd5,
|
||||
0xba, 0xff, 0xe1, 0xdb, 0x9b, 0x66, 0xee, 0x9f, 0x37, 0xcd, 0xdc, 0xbf, 0x6f, 0x9a, 0xc6, 0x9f,
|
||||
0x56, 0x4d, 0xe3, 0xed, 0xaa, 0x69, 0xfc, 0x63, 0xd5, 0x34, 0xfe, 0xb5, 0x6a, 0x1a, 0x67, 0x45,
|
||||
0xf5, 0x8b, 0xfc, 0xf3, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xc3, 0x59, 0xc9, 0x0e, 0x81, 0x0f,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -225,6 +225,12 @@ message ContainerSpec {
|
|||
|
||||
// DNSConfig allows one to specify DNS related configuration in resolv.conf
|
||||
DNSConfig dns_config = 15 [(gogoproto.customname) = "DNSConfig"];
|
||||
|
||||
// Healthcheck describes how to check the container is healthy. If the
|
||||
// container is considered unhealthy, it will be destroyed, its creating
|
||||
// task will exit and a new task will be rescheduled elsewhere. A container
|
||||
// is considered unhealthy after `Retries` number of consecutive failures.
|
||||
HealthConfig healthcheck = 16;
|
||||
}
|
||||
|
||||
// EndpointSpec defines the properties that can be configured to
|
||||
|
|
1135
vendor/src/github.com/docker/swarmkit/api/types.pb.go
vendored
1135
vendor/src/github.com/docker/swarmkit/api/types.pb.go
vendored
File diff suppressed because it is too large
Load diff
|
@ -408,6 +408,12 @@ message ContainerStatus {
|
|||
int32 exit_code = 3;
|
||||
}
|
||||
|
||||
// PortStatus specifies the actual allocated runtime state of a list
|
||||
// of port configs.
|
||||
message PortStatus {
|
||||
repeated PortConfig ports = 1;
|
||||
}
|
||||
|
||||
message TaskStatus {
|
||||
Timestamp timestamp = 1;
|
||||
|
||||
|
@ -438,6 +444,10 @@ message TaskStatus {
|
|||
oneof runtime_status {
|
||||
ContainerStatus container = 5;
|
||||
}
|
||||
|
||||
// HostPorts provides a list of ports allocated at the host
|
||||
// level.
|
||||
PortStatus port_status = 6;
|
||||
}
|
||||
|
||||
// NetworkAttachmentConfig specifies how a service should be attached to a particular network.
|
||||
|
@ -807,3 +817,26 @@ message BlacklistedCertificate {
|
|||
// was issued for the given CN.
|
||||
Timestamp expiry = 1;
|
||||
}
|
||||
|
||||
// HealthConfig holds configuration settings for the HEALTHCHECK feature.
|
||||
message HealthConfig {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
// The options are:
|
||||
// {} : inherit healthcheck
|
||||
// {"NONE"} : disable healthcheck
|
||||
// {"CMD", args...} : exec arguments directly
|
||||
// {"CMD-SHELL", command} : run command with system's default shell
|
||||
repeated string test = 1;
|
||||
|
||||
// Interval is the time to wait between checks. Zero means inherit.
|
||||
Duration interval = 2;
|
||||
|
||||
// Timeout is the time to wait before considering the check to have hung.
|
||||
// Zero means inherit.
|
||||
Duration timeout = 3;
|
||||
|
||||
// Retries is the number of consecutive failures needed to consider a
|
||||
// container as unhealthy. Zero means inherit.
|
||||
int32 retries = 4;
|
||||
}
|
||||
|
|
|
@ -489,6 +489,28 @@ func taskUpdateEndpoint(t *api.Task, endpoint *api.Endpoint) {
|
|||
t.Endpoint = endpoint.Copy()
|
||||
}
|
||||
|
||||
func isIngressNetworkNeeded(s *api.Service) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.Spec.Endpoint == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, p := range s.Spec.Endpoint.Ports {
|
||||
// The service to which this task belongs is trying to
|
||||
// expose ports with PublishMode as Ingress to the
|
||||
// external world. Automatically attach the task to
|
||||
// the ingress network.
|
||||
if p.PublishMode == api.PublishModeIngress {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *Allocator) taskCreateNetworkAttachments(t *api.Task, s *api.Service) {
|
||||
// If task network attachments have already been filled in no
|
||||
// need to do anything else.
|
||||
|
@ -497,11 +519,7 @@ func (a *Allocator) taskCreateNetworkAttachments(t *api.Task, s *api.Service) {
|
|||
}
|
||||
|
||||
var networks []*api.NetworkAttachment
|
||||
|
||||
// The service to which this task belongs is trying to expose
|
||||
// ports to the external world. Automatically attach the task
|
||||
// to the ingress network.
|
||||
if s != nil && s.Spec.Endpoint != nil && len(s.Spec.Endpoint.Ports) != 0 {
|
||||
if isIngressNetworkNeeded(s) {
|
||||
networks = append(networks, &api.NetworkAttachment{Network: a.netCtx.ingressNetwork})
|
||||
}
|
||||
|
||||
|
@ -638,7 +656,7 @@ func (a *Allocator) allocateService(ctx context.Context, s *api.Service) error {
|
|||
// The service is trying to expose ports to the external
|
||||
// world. Automatically attach the service to the ingress
|
||||
// network only if it is not already done.
|
||||
if len(s.Spec.Endpoint.Ports) != 0 {
|
||||
if isIngressNetworkNeeded(s) {
|
||||
var found bool
|
||||
for _, vip := range s.Endpoint.VirtualIPs {
|
||||
if vip.NetworkID == nc.ingressNetwork.ID {
|
||||
|
@ -668,7 +686,7 @@ func (a *Allocator) allocateService(ctx context.Context, s *api.Service) error {
|
|||
// If the service doesn't expose ports any more and if we have
|
||||
// any lingering virtual IP references for ingress network
|
||||
// clean them up here.
|
||||
if s.Spec.Endpoint == nil || len(s.Spec.Endpoint.Ports) == 0 {
|
||||
if !isIngressNetworkNeeded(s) {
|
||||
if s.Endpoint != nil {
|
||||
for i, vip := range s.Endpoint.VirtualIPs {
|
||||
if vip.NetworkID == nc.ingressNetwork.ID {
|
||||
|
@ -755,7 +773,10 @@ func (a *Allocator) allocateTask(ctx context.Context, t *api.Task) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
taskUpdateEndpoint(t, s.Endpoint)
|
||||
if s.Endpoint != nil {
|
||||
taskUpdateEndpoint(t, s.Endpoint)
|
||||
taskUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, na := range t.Networks {
|
||||
|
@ -781,6 +802,7 @@ func (a *Allocator) allocateTask(ctx context.Context, t *api.Task) (err error) {
|
|||
taskUpdated = true
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -128,8 +128,12 @@ func (pa *portAllocator) serviceAllocatePorts(s *api.Service) (err error) {
|
|||
for _, portConfig := range portConfigs {
|
||||
// Make a copy of port config to create runtime state
|
||||
portState := portConfig.Copy()
|
||||
if err = pa.portSpaces[portState.Protocol].allocate(portState); err != nil {
|
||||
return
|
||||
|
||||
// Do an actual allocation only if the PublishMode is Ingress
|
||||
if portConfig.PublishMode == api.PublishModeIngress {
|
||||
if err = pa.portSpaces[portState.Protocol].allocate(portState); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s.Endpoint == nil {
|
||||
|
@ -148,6 +152,12 @@ func (pa *portAllocator) serviceDeallocatePorts(s *api.Service) {
|
|||
}
|
||||
|
||||
for _, portState := range s.Endpoint.Ports {
|
||||
// Do an actual free only if the PublishMode is
|
||||
// Ingress
|
||||
if portState.PublishMode != api.PublishModeIngress {
|
||||
continue
|
||||
}
|
||||
|
||||
pa.portSpaces[portState.Protocol].free(portState)
|
||||
}
|
||||
|
||||
|
@ -177,6 +187,11 @@ func (pa *portAllocator) isPortsAllocated(s *api.Service) bool {
|
|||
}
|
||||
|
||||
for i, portConfig := range s.Spec.Endpoint.Ports {
|
||||
// Ignore ports which are not PublishModeIngress
|
||||
if portConfig.PublishMode != api.PublishModeIngress {
|
||||
continue
|
||||
}
|
||||
|
||||
// The port configuration slice and port state slice
|
||||
// are expected to be in the same order.
|
||||
portState := s.Endpoint.Ports[i]
|
||||
|
|
|
@ -131,11 +131,13 @@ func New(config *Config) (*Manager, error) {
|
|||
// externally-reachable address.
|
||||
tcpAddr := config.AdvertiseAddr
|
||||
|
||||
var tcpAddrPort string
|
||||
if tcpAddr == "" {
|
||||
// Otherwise, we know we are joining an existing swarm. Use a
|
||||
// wildcard address to trigger remote autodetection of our
|
||||
// address.
|
||||
_, tcpAddrPort, err := net.SplitHostPort(config.ProtoAddr["tcp"])
|
||||
var err error
|
||||
_, tcpAddrPort, err = net.SplitHostPort(config.ProtoAddr["tcp"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing or invalid listen address %s", config.ProtoAddr["tcp"])
|
||||
}
|
||||
|
@ -189,7 +191,7 @@ func New(config *Config) (*Manager, error) {
|
|||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proto == "tcp" {
|
||||
if proto == "tcp" && tcpAddrPort == "0" {
|
||||
// in case of 0 port
|
||||
tcpAddr = l.Addr().String()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue