github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/api/client/service/opts.go (about)

     1  package service
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"math/big"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/docker/docker/opts"
    12  	runconfigopts "github.com/docker/docker/runconfig/opts"
    13  	"github.com/docker/engine-api/types/swarm"
    14  	"github.com/docker/go-connections/nat"
    15  	units "github.com/docker/go-units"
    16  	"github.com/spf13/cobra"
    17  )
    18  
    19  type int64Value interface {
    20  	Value() int64
    21  }
    22  
    23  type memBytes int64
    24  
    25  func (m *memBytes) String() string {
    26  	return units.BytesSize(float64(m.Value()))
    27  }
    28  
    29  func (m *memBytes) Set(value string) error {
    30  	val, err := units.RAMInBytes(value)
    31  	*m = memBytes(val)
    32  	return err
    33  }
    34  
    35  func (m *memBytes) Type() string {
    36  	return "MemoryBytes"
    37  }
    38  
    39  func (m *memBytes) Value() int64 {
    40  	return int64(*m)
    41  }
    42  
    43  type nanoCPUs int64
    44  
    45  func (c *nanoCPUs) String() string {
    46  	return big.NewRat(c.Value(), 1e9).FloatString(3)
    47  }
    48  
    49  func (c *nanoCPUs) Set(value string) error {
    50  	cpu, ok := new(big.Rat).SetString(value)
    51  	if !ok {
    52  		return fmt.Errorf("Failed to parse %v as a rational number", value)
    53  	}
    54  	nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
    55  	if !nano.IsInt() {
    56  		return fmt.Errorf("value is too precise")
    57  	}
    58  	*c = nanoCPUs(nano.Num().Int64())
    59  	return nil
    60  }
    61  
    62  func (c *nanoCPUs) Type() string {
    63  	return "NanoCPUs"
    64  }
    65  
    66  func (c *nanoCPUs) Value() int64 {
    67  	return int64(*c)
    68  }
    69  
    70  // DurationOpt is an option type for time.Duration that uses a pointer. This
    71  // allows us to get nil values outside, instead of defaulting to 0
    72  type DurationOpt struct {
    73  	value *time.Duration
    74  }
    75  
    76  // Set a new value on the option
    77  func (d *DurationOpt) Set(s string) error {
    78  	v, err := time.ParseDuration(s)
    79  	d.value = &v
    80  	return err
    81  }
    82  
    83  // Type returns the type of this option
    84  func (d *DurationOpt) Type() string {
    85  	return "duration-ptr"
    86  }
    87  
    88  // String returns a string repr of this option
    89  func (d *DurationOpt) String() string {
    90  	if d.value != nil {
    91  		return d.value.String()
    92  	}
    93  	return "none"
    94  }
    95  
    96  // Value returns the time.Duration
    97  func (d *DurationOpt) Value() *time.Duration {
    98  	return d.value
    99  }
   100  
   101  // Uint64Opt represents a uint64.
   102  type Uint64Opt struct {
   103  	value *uint64
   104  }
   105  
   106  // Set a new value on the option
   107  func (i *Uint64Opt) Set(s string) error {
   108  	v, err := strconv.ParseUint(s, 0, 64)
   109  	i.value = &v
   110  	return err
   111  }
   112  
   113  // Type returns the type of this option
   114  func (i *Uint64Opt) Type() string {
   115  	return "uint64-ptr"
   116  }
   117  
   118  // String returns a string repr of this option
   119  func (i *Uint64Opt) String() string {
   120  	if i.value != nil {
   121  		return fmt.Sprintf("%v", *i.value)
   122  	}
   123  	return "none"
   124  }
   125  
   126  // Value returns the uint64
   127  func (i *Uint64Opt) Value() *uint64 {
   128  	return i.value
   129  }
   130  
   131  // MountOpt is a Value type for parsing mounts
   132  type MountOpt struct {
   133  	values []swarm.Mount
   134  }
   135  
   136  // Set a new mount value
   137  func (m *MountOpt) Set(value string) error {
   138  	csvReader := csv.NewReader(strings.NewReader(value))
   139  	fields, err := csvReader.Read()
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	mount := swarm.Mount{}
   145  
   146  	volumeOptions := func() *swarm.VolumeOptions {
   147  		if mount.VolumeOptions == nil {
   148  			mount.VolumeOptions = &swarm.VolumeOptions{
   149  				Labels: make(map[string]string),
   150  			}
   151  		}
   152  		if mount.VolumeOptions.DriverConfig == nil {
   153  			mount.VolumeOptions.DriverConfig = &swarm.Driver{}
   154  		}
   155  		return mount.VolumeOptions
   156  	}
   157  
   158  	bindOptions := func() *swarm.BindOptions {
   159  		if mount.BindOptions == nil {
   160  			mount.BindOptions = new(swarm.BindOptions)
   161  		}
   162  		return mount.BindOptions
   163  	}
   164  
   165  	setValueOnMap := func(target map[string]string, value string) {
   166  		parts := strings.SplitN(value, "=", 2)
   167  		if len(parts) == 1 {
   168  			target[value] = ""
   169  		} else {
   170  			target[parts[0]] = parts[1]
   171  		}
   172  	}
   173  
   174  	mount.Type = swarm.MountTypeVolume // default to volume mounts
   175  	// Set writable as the default
   176  	for _, field := range fields {
   177  		parts := strings.SplitN(field, "=", 2)
   178  		key := strings.ToLower(parts[0])
   179  
   180  		if len(parts) == 1 {
   181  			switch key {
   182  			case "readonly", "ro":
   183  				mount.ReadOnly = true
   184  				continue
   185  			case "volume-nocopy":
   186  				volumeOptions().NoCopy = true
   187  				continue
   188  			}
   189  		}
   190  
   191  		if len(parts) != 2 {
   192  			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
   193  		}
   194  
   195  		value := parts[1]
   196  		switch key {
   197  		case "type":
   198  			mount.Type = swarm.MountType(strings.ToLower(value))
   199  		case "source", "src":
   200  			mount.Source = value
   201  		case "target", "dst", "destination":
   202  			mount.Target = value
   203  		case "readonly", "ro":
   204  			mount.ReadOnly, err = strconv.ParseBool(value)
   205  			if err != nil {
   206  				return fmt.Errorf("无效值 %s: %s", key, value)
   207  			}
   208  		case "bind-propagation":
   209  			bindOptions().Propagation = swarm.MountPropagation(strings.ToLower(value))
   210  		case "volume-nocopy":
   211  			volumeOptions().NoCopy, err = strconv.ParseBool(value)
   212  			if err != nil {
   213  				return fmt.Errorf("invalid value for populate: %s", value)
   214  			}
   215  		case "volume-label":
   216  			setValueOnMap(volumeOptions().Labels, value)
   217  		case "volume-driver":
   218  			volumeOptions().DriverConfig.Name = value
   219  		case "volume-opt":
   220  			if volumeOptions().DriverConfig.Options == nil {
   221  				volumeOptions().DriverConfig.Options = make(map[string]string)
   222  			}
   223  			setValueOnMap(volumeOptions().DriverConfig.Options, value)
   224  		default:
   225  			return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
   226  		}
   227  	}
   228  
   229  	if mount.Type == "" {
   230  		return fmt.Errorf("挂载类型不能为空")
   231  	}
   232  
   233  	if mount.Target == "" {
   234  		return fmt.Errorf("挂载目的地址不能为空")
   235  	}
   236  
   237  	if mount.VolumeOptions != nil && mount.Source == "" {
   238  		return fmt.Errorf("当指定选项 volume-* 时,挂载的源地址不能为空")
   239  	}
   240  
   241  	if mount.Type == swarm.MountTypeBind && mount.VolumeOptions != nil {
   242  		return fmt.Errorf("不能将 'volume-*' 选项和挂载类型 '%s' 混用", swarm.MountTypeBind)
   243  	}
   244  	if mount.Type == swarm.MountTypeVolume && mount.BindOptions != nil {
   245  		return fmt.Errorf("不能将 'bind-*' 选项和挂载类型 '%s' 混用", swarm.MountTypeVolume)
   246  	}
   247  
   248  	m.values = append(m.values, mount)
   249  	return nil
   250  }
   251  
   252  // Type returns the type of this option
   253  func (m *MountOpt) Type() string {
   254  	return "mount"
   255  }
   256  
   257  // String returns a string repr of this option
   258  func (m *MountOpt) String() string {
   259  	mounts := []string{}
   260  	for _, mount := range m.values {
   261  		repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
   262  		mounts = append(mounts, repr)
   263  	}
   264  	return strings.Join(mounts, ", ")
   265  }
   266  
   267  // Value returns the mounts
   268  func (m *MountOpt) Value() []swarm.Mount {
   269  	return m.values
   270  }
   271  
   272  type updateOptions struct {
   273  	parallelism uint64
   274  	delay       time.Duration
   275  	onFailure   string
   276  }
   277  
   278  type resourceOptions struct {
   279  	limitCPU      nanoCPUs
   280  	limitMemBytes memBytes
   281  	resCPU        nanoCPUs
   282  	resMemBytes   memBytes
   283  }
   284  
   285  func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
   286  	return &swarm.ResourceRequirements{
   287  		Limits: &swarm.Resources{
   288  			NanoCPUs:    r.limitCPU.Value(),
   289  			MemoryBytes: r.limitMemBytes.Value(),
   290  		},
   291  		Reservations: &swarm.Resources{
   292  			NanoCPUs:    r.resCPU.Value(),
   293  			MemoryBytes: r.resMemBytes.Value(),
   294  		},
   295  	}
   296  }
   297  
   298  type restartPolicyOptions struct {
   299  	condition   string
   300  	delay       DurationOpt
   301  	maxAttempts Uint64Opt
   302  	window      DurationOpt
   303  }
   304  
   305  func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
   306  	return &swarm.RestartPolicy{
   307  		Condition:   swarm.RestartPolicyCondition(r.condition),
   308  		Delay:       r.delay.Value(),
   309  		MaxAttempts: r.maxAttempts.Value(),
   310  		Window:      r.window.Value(),
   311  	}
   312  }
   313  
   314  func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
   315  	nets := []swarm.NetworkAttachmentConfig{}
   316  	for _, network := range networks {
   317  		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
   318  	}
   319  	return nets
   320  }
   321  
   322  type endpointOptions struct {
   323  	mode  string
   324  	ports opts.ListOpts
   325  }
   326  
   327  func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
   328  	portConfigs := []swarm.PortConfig{}
   329  	// We can ignore errors because the format was already validated by ValidatePort
   330  	ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
   331  
   332  	for port := range ports {
   333  		portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
   334  	}
   335  
   336  	return &swarm.EndpointSpec{
   337  		Mode:  swarm.ResolutionMode(strings.ToLower(e.mode)),
   338  		Ports: portConfigs,
   339  	}
   340  }
   341  
   342  func convertPortToPortConfig(
   343  	port nat.Port,
   344  	portBindings map[nat.Port][]nat.PortBinding,
   345  ) []swarm.PortConfig {
   346  	ports := []swarm.PortConfig{}
   347  
   348  	for _, binding := range portBindings[port] {
   349  		hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
   350  		ports = append(ports, swarm.PortConfig{
   351  			//TODO Name: ?
   352  			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
   353  			TargetPort:    uint32(port.Int()),
   354  			PublishedPort: uint32(hostPort),
   355  		})
   356  	}
   357  	return ports
   358  }
   359  
   360  type logDriverOptions struct {
   361  	name string
   362  	opts opts.ListOpts
   363  }
   364  
   365  func newLogDriverOptions() logDriverOptions {
   366  	return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
   367  }
   368  
   369  func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
   370  	if ldo.name == "" {
   371  		return nil
   372  	}
   373  
   374  	// set the log driver only if specified.
   375  	return &swarm.Driver{
   376  		Name:    ldo.name,
   377  		Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()),
   378  	}
   379  }
   380  
   381  // ValidatePort validates a string is in the expected format for a port definition
   382  func ValidatePort(value string) (string, error) {
   383  	portMappings, err := nat.ParsePortSpec(value)
   384  	for _, portMapping := range portMappings {
   385  		if portMapping.Binding.HostIP != "" {
   386  			return "", fmt.Errorf("服务不支持宿主机IP.")
   387  		}
   388  	}
   389  	return value, err
   390  }
   391  
   392  type serviceOptions struct {
   393  	name            string
   394  	labels          opts.ListOpts
   395  	containerLabels opts.ListOpts
   396  	image           string
   397  	args            []string
   398  	env             opts.ListOpts
   399  	workdir         string
   400  	user            string
   401  	mounts          MountOpt
   402  
   403  	resources resourceOptions
   404  	stopGrace DurationOpt
   405  
   406  	replicas Uint64Opt
   407  	mode     string
   408  
   409  	restartPolicy restartPolicyOptions
   410  	constraints   []string
   411  	update        updateOptions
   412  	networks      []string
   413  	endpoint      endpointOptions
   414  
   415  	registryAuth bool
   416  
   417  	logDriver logDriverOptions
   418  }
   419  
   420  func newServiceOptions() *serviceOptions {
   421  	return &serviceOptions{
   422  		labels:          opts.NewListOpts(runconfigopts.ValidateEnv),
   423  		containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv),
   424  		env:             opts.NewListOpts(runconfigopts.ValidateEnv),
   425  		endpoint: endpointOptions{
   426  			ports: opts.NewListOpts(ValidatePort),
   427  		},
   428  		logDriver: newLogDriverOptions(),
   429  	}
   430  }
   431  
   432  func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
   433  	var service swarm.ServiceSpec
   434  
   435  	service = swarm.ServiceSpec{
   436  		Annotations: swarm.Annotations{
   437  			Name:   opts.name,
   438  			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
   439  		},
   440  		TaskTemplate: swarm.TaskSpec{
   441  			ContainerSpec: swarm.ContainerSpec{
   442  				Image:           opts.image,
   443  				Args:            opts.args,
   444  				Env:             opts.env.GetAll(),
   445  				Labels:          runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
   446  				Dir:             opts.workdir,
   447  				User:            opts.user,
   448  				Mounts:          opts.mounts.Value(),
   449  				StopGracePeriod: opts.stopGrace.Value(),
   450  			},
   451  			Resources:     opts.resources.ToResourceRequirements(),
   452  			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
   453  			Placement: &swarm.Placement{
   454  				Constraints: opts.constraints,
   455  			},
   456  			LogDriver: opts.logDriver.toLogDriver(),
   457  		},
   458  		Mode: swarm.ServiceMode{},
   459  		UpdateConfig: &swarm.UpdateConfig{
   460  			Parallelism:   opts.update.parallelism,
   461  			Delay:         opts.update.delay,
   462  			FailureAction: opts.update.onFailure,
   463  		},
   464  		Networks:     convertNetworks(opts.networks),
   465  		EndpointSpec: opts.endpoint.ToEndpointSpec(),
   466  	}
   467  
   468  	switch opts.mode {
   469  	case "global":
   470  		if opts.replicas.Value() != nil {
   471  			return service, fmt.Errorf("副本数(replicas)只能被使用于副本(replicated)模式")
   472  		}
   473  
   474  		service.Mode.Global = &swarm.GlobalService{}
   475  	case "replicated":
   476  		service.Mode.Replicated = &swarm.ReplicatedService{
   477  			Replicas: opts.replicas.Value(),
   478  		}
   479  	default:
   480  		return service, fmt.Errorf("未知的服务模式: %s", opts.mode)
   481  	}
   482  	return service, nil
   483  }
   484  
   485  // addServiceFlags adds all flags that are common to both `create` and `update`.
   486  // Any flags that are not common are added separately in the individual command
   487  func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
   488  	flags := cmd.Flags()
   489  	flags.StringVar(&opts.name, flagName, "", "服务名")
   490  
   491  	flags.StringVarP(&opts.workdir, "workdir", "w", "", "容器内部的工作目录")
   492  	flags.StringVarP(&opts.user, flagUser, "u", "", "用户名UID (格式: <用户名|用户名ID>[:<组|组ID>])")
   493  
   494  	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "限制CPU使用量")
   495  	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "限制内存使用量")
   496  	flags.Var(&opts.resources.resCPU, flagReserveCPU, "预留CPU使用量")
   497  	flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "预留内存使用量")
   498  	flags.Var(&opts.stopGrace, flagStopGracePeriod, "强制终止容器前的等待时间")
   499  
   500  	flags.Var(&opts.replicas, flagReplicas, "服务内任务数量")
   501  
   502  	flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "条件满足时的重启策略:none(无), on-failure(失败时重启), 或者其他)")
   503  	flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "两次重启之间的延时")
   504  	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "放弃重启前的最大次数")
   505  	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "评估重启策略的窗口时间")
   506  
   507  	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "并发更新任务时的最大数量(0代表立即更新所有任务)")
   508  	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "两次更新之间的延时")
   509  	flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", "更新失败时的策略: pause(暂停)|continue(继续)")
   510  
   511  	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "网络模式: vip(虚拟IP)|dnsrr(DNS轮询)")
   512  
   513  	flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "向Swarm集群中节点上的代理模块发送注册认证信息")
   514  
   515  	flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "服务的日志驱动")
   516  	flags.Var(&opts.logDriver.opts, flagLogOpt, "日志驱动选项")
   517  }
   518  
   519  const (
   520  	flagConstraint           = "constraint"
   521  	flagConstraintRemove     = "constraint-rm"
   522  	flagConstraintAdd        = "constraint-add"
   523  	flagContainerLabel       = "container-label"
   524  	flagContainerLabelRemove = "container-label-rm"
   525  	flagContainerLabelAdd    = "container-label-add"
   526  	flagEndpointMode         = "endpoint-mode"
   527  	flagEnv                  = "env"
   528  	flagEnvRemove            = "env-rm"
   529  	flagEnvAdd               = "env-add"
   530  	flagLabel                = "label"
   531  	flagLabelRemove          = "label-rm"
   532  	flagLabelAdd             = "label-add"
   533  	flagLimitCPU             = "limit-cpu"
   534  	flagLimitMemory          = "limit-memory"
   535  	flagMode                 = "mode"
   536  	flagMount                = "mount"
   537  	flagMountRemove          = "mount-rm"
   538  	flagMountAdd             = "mount-add"
   539  	flagName                 = "name"
   540  	flagNetwork              = "network"
   541  	flagPublish              = "publish"
   542  	flagPublishRemove        = "publish-rm"
   543  	flagPublishAdd           = "publish-add"
   544  	flagReplicas             = "replicas"
   545  	flagReserveCPU           = "reserve-cpu"
   546  	flagReserveMemory        = "reserve-memory"
   547  	flagRestartCondition     = "restart-condition"
   548  	flagRestartDelay         = "restart-delay"
   549  	flagRestartMaxAttempts   = "restart-max-attempts"
   550  	flagRestartWindow        = "restart-window"
   551  	flagStopGracePeriod      = "stop-grace-period"
   552  	flagUpdateDelay          = "update-delay"
   553  	flagUpdateFailureAction  = "update-failure-action"
   554  	flagUpdateParallelism    = "update-parallelism"
   555  	flagUser                 = "user"
   556  	flagRegistryAuth         = "with-registry-auth"
   557  	flagLogDriver            = "log-driver"
   558  	flagLogOpt               = "log-opt"
   559  )