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 )