From 7c773c1ba162ad02a0152e8f77d31f7584915521 Mon Sep 17 00:00:00 2001 From: Rudy Zhang Date: Mon, 11 Jun 2018 23:23:55 +0800 Subject: [PATCH] feature: add update daemon config function Add update daemon config function. If pouchd is alive, it will change daemon config if it supports, and then change the config file. If pouchd is dead, it just update the daemon config file. Pouchd dead: You can specified the config file by set `--config-file` to update, more functions you can see by `pouch updatedaemon --help` Signed-off-by: Rudy Zhang --- apis/swagger.yml | 5 + apis/types/registry_service_config.go | 8 +- cli/main.go | 1 + cli/update_daemon.go | 188 ++++++++++++++++++++++++++ client/client.go | 4 +- client/daemon_update.go | 18 +++ client/daemon_update_test.go | 51 +++++++ client/interface.go | 1 + cri/config/config.go | 10 +- daemon/config/config.go | 47 ++----- main.go | 1 + network/config.go | 28 ++-- network/mode/bridge/bridge.go | 7 +- storage/volume/config.go | 10 +- test/z_cli_daemon_test.go | 16 +++ 15 files changed, 329 insertions(+), 66 deletions(-) create mode 100644 cli/update_daemon.go create mode 100644 client/daemon_update.go create mode 100644 client/daemon_update_test.go diff --git a/apis/swagger.yml b/apis/swagger.yml index 651ccb9ba..a963253f1 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -1590,6 +1590,7 @@ definitions: > are in compliance with any terms that cover redistributing > nondistributable artifacts. + x-omitempty: true type: "array" items: type: "string" @@ -1616,6 +1617,7 @@ definitions: > feature to push artifacts to private registries and ensure that you > are in compliance with any terms that cover redistributing > nondistributable artifacts. + x-omitempty: true type: "array" items: type: "string" @@ -1645,11 +1647,13 @@ definitions: > should therefore ONLY be used for testing purposes. For increased > security, users should add their CA to their system's list of trusted > CAs instead of enabling this option. + x-omitempty: true type: "array" items: type: "string" example: ["::1/128", "127.0.0.0/8"] IndexConfigs: + x-omitempty: true type: "object" additionalProperties: $ref: "#/definitions/IndexInfo" @@ -1671,6 +1675,7 @@ definitions: Official: false Mirrors: description: "List of registry URLs that act as a mirror for the official registry." + x-omitempty: true type: "array" items: type: "string" diff --git a/apis/types/registry_service_config.go b/apis/types/registry_service_config.go index 3f334ea97..31406054a 100644 --- a/apis/types/registry_service_config.go +++ b/apis/types/registry_service_config.go @@ -41,7 +41,7 @@ type RegistryServiceConfig struct { // > are in compliance with any terms that cover redistributing // > nondistributable artifacts. // - AllowNondistributableArtifactsCIDRs []string `json:"AllowNondistributableArtifactsCIDRs"` + AllowNondistributableArtifactsCIDRs []string `json:"AllowNondistributableArtifactsCIDRs,omitempty"` // List of registry hostnames to which nondistributable artifacts can be // pushed, using the format `[:]` or `[:]`. @@ -64,7 +64,7 @@ type RegistryServiceConfig struct { // > are in compliance with any terms that cover redistributing // > nondistributable artifacts. // - AllowNondistributableArtifactsHostnames []string `json:"AllowNondistributableArtifactsHostnames"` + AllowNondistributableArtifactsHostnames []string `json:"AllowNondistributableArtifactsHostnames,omitempty"` // index configs IndexConfigs map[string]IndexInfo `json:"IndexConfigs,omitempty"` @@ -93,10 +93,10 @@ type RegistryServiceConfig struct { // > security, users should add their CA to their system's list of trusted // > CAs instead of enabling this option. // - InsecureRegistryCIDRs []string `json:"InsecureRegistryCIDRs"` + InsecureRegistryCIDRs []string `json:"InsecureRegistryCIDRs,omitempty"` // List of registry URLs that act as a mirror for the official registry. - Mirrors []string `json:"Mirrors"` + Mirrors []string `json:"Mirrors,omitempty"` } /* polymorph RegistryServiceConfig AllowNondistributableArtifactsCIDRs false */ diff --git a/cli/main.go b/cli/main.go index bcb0b41f9..7f1c41e0e 100644 --- a/cli/main.go +++ b/cli/main.go @@ -45,6 +45,7 @@ func main() { cli.AddCommand(base, &LogsCommand{}) cli.AddCommand(base, &RemountLxcfsCommand{}) cli.AddCommand(base, &WaitCommand{}) + cli.AddCommand(base, &DaemonUpdateCommand{}) // add generate doc command cli.AddCommand(base, &GenDocCommand{}) diff --git a/cli/update_daemon.go b/cli/update_daemon.go new file mode 100644 index 000000000..5250733ec --- /dev/null +++ b/cli/update_daemon.go @@ -0,0 +1,188 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "io" + "io/ioutil" + "os" + + "github.com/alibaba/pouch/apis/types" + "github.com/alibaba/pouch/daemon/config" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// updateDescription is used to describe updatedaemon command in detail and auto generate command doc. +var daemonUpdateDescription = "Update daemon's configurations, if daemon is stoped, it will just update config file. " + + "Online update just including: image proxy, label, offline update including: manager white list, debug level, " + + "execute root directory, bridge name, bridge IP, fixed CIDR, defaut gateway, iptables, ipforwark, userland proxy" + + "If pouchd is alive, you can only use --offline=true to update config file" + +// DaemonUpdateCommand use to implement 'updatedaemon' command, it modifies the configurations of a container. +type DaemonUpdateCommand struct { + baseCommand + + configFile string + offline bool + + debug bool + imageProxy string + label []string + managerWhiteList string + execRoot string + bridgeName string + bridgeIP string + fixedCIDR string + defaultGateway string + iptables bool + ipforward bool + userlandProxy bool +} + +// Init initialize updatedaemon command. +func (udc *DaemonUpdateCommand) Init(c *Cli) { + udc.cli = c + udc.cmd = &cobra.Command{ + Use: "updatedaemon [OPTIONS]", + Short: "Update the configurations of pouchd", + Long: daemonUpdateDescription, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return udc.daemonUpdateRun(args) + }, + Example: daemonUpdateExample(), + } + udc.addFlags() +} + +// addFlags adds flags for specific command. +func (udc *DaemonUpdateCommand) addFlags() { + flagSet := udc.cmd.Flags() + flagSet.SetInterspersed(false) + flagSet.StringVar(&udc.configFile, "config-file", "/etc/pouch/config.json", "specified config file for updating daemon") + flagSet.BoolVar(&udc.offline, "offline", false, "just update daemon config file") + + flagSet.BoolVar(&udc.debug, "debug", false, "update daemon debug mode") + flagSet.StringVar(&udc.imageProxy, "image-proxy", "", "update daemon image proxy") + flagSet.StringVar(&udc.managerWhiteList, "manager-white-list", "", "update daemon manager white list") + flagSet.StringSliceVar(&udc.label, "label", nil, "update daemon labels") + flagSet.StringVar(&udc.execRoot, "exec-root-dir", "", "update exec root directory for network") + flagSet.StringVar(&udc.bridgeName, "bridge-name", "", "update daemon bridge device") + flagSet.StringVar(&udc.bridgeIP, "bip", "", "update daemon bridge IP") + flagSet.StringVar(&udc.fixedCIDR, "fixed-cidr", "", "update daemon bridge fixed CIDR") + flagSet.StringVar(&udc.defaultGateway, "default-gateway", "", "update daemon bridge default gateway") + flagSet.BoolVar(&udc.iptables, "iptables", true, "update daemon with iptables") + flagSet.BoolVar(&udc.ipforward, "ipforward", true, "udpate daemon with ipforward") + flagSet.BoolVar(&udc.userlandProxy, "userland-proxy", false, "update daemon with userland proxy") +} + +// daemonUpdateRun is the entry of updatedaemon command. +func (udc *DaemonUpdateCommand) daemonUpdateRun(args []string) error { + ctx := context.Background() + + apiClient := udc.cli.Client() + + msg, err := apiClient.SystemPing(ctx) + if !udc.offline && err == nil && msg == "OK" { + // TODO: daemon support more configures for update online, such as debug level. + daemonConfig := &types.DaemonUpdateConfig{ + ImageProxy: udc.imageProxy, + Labels: udc.label, + } + + err = apiClient.DaemonUpdate(ctx, daemonConfig) + if err != nil { + return errors.Wrap(err, "failed to update alive daemon config") + } + } else { + // offline update config file. + err = udc.updateDaemonConfigFile() + if err != nil { + return errors.Wrap(err, "failed to update daemon config file.") + } + } + + return nil +} + +func (udc *DaemonUpdateCommand) updateDaemonConfigFile() error { + // read config from file. + contents, err := ioutil.ReadFile(udc.configFile) + if err != nil { + return errors.Wrapf(err, "failed to read config file(%s)", udc.configFile) + } + + daemonConfig := &config.Config{} + if err = json.NewDecoder(bytes.NewReader(contents)).Decode(daemonConfig); err != nil { + return errors.Wrap(err, "failed to decode json: %s") + } + + flagSet := udc.cmd.Flags() + + if flagSet.Changed("image-proxy") { + daemonConfig.ImageProxy = udc.imageProxy + } + + if flagSet.Changed("manager-white-list") { + daemonConfig.TLS.ManagerWhiteList = udc.managerWhiteList + } + + // TODO: add parse labels + + if flagSet.Changed("exec-root-dir") { + daemonConfig.NetworkConfig.ExecRoot = udc.execRoot + } + + if flagSet.Changed("bridge-name") { + daemonConfig.NetworkConfig.BridgeConfig.Name = udc.bridgeName + } + + if flagSet.Changed("bip") { + daemonConfig.NetworkConfig.BridgeConfig.IP = udc.bridgeIP + } + + if flagSet.Changed("fixed-cidr") { + daemonConfig.NetworkConfig.BridgeConfig.FixedCIDR = udc.fixedCIDR + } + + if flagSet.Changed("default-gateway") { + daemonConfig.NetworkConfig.BridgeConfig.GatewayIPv4 = udc.defaultGateway + } + + if flagSet.Changed("iptables") { + daemonConfig.NetworkConfig.BridgeConfig.IPTables = udc.iptables + } + + if flagSet.Changed("ipforward") { + daemonConfig.NetworkConfig.BridgeConfig.IPForward = udc.ipforward + } + + if flagSet.Changed("userland-proxy") { + daemonConfig.NetworkConfig.BridgeConfig.UserlandProxy = udc.userlandProxy + } + + // write config to file + fd, err := os.OpenFile(udc.configFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return errors.Wrapf(err, "failed to open config file(%s)", udc.configFile) + } + defer fd.Close() + + fd.Seek(0, io.SeekStart) + encoder := json.NewEncoder(fd) + encoder.SetIndent("", " ") + err = encoder.Encode(daemonConfig) + if err != nil { + return errors.Wrapf(err, "failed to write config to file(%s)", udc.configFile) + } + + return nil +} + +// daemonUpdateExample shows examples in updatedaemon command, and is used in auto-generated cli docs. +func daemonUpdateExample() string { + return `$ pouch updatedaemon --debug=true` +} diff --git a/client/client.go b/client/client.go index b5c8b0cec..a34d37087 100644 --- a/client/client.go +++ b/client/client.go @@ -35,8 +35,8 @@ type TLSConfig struct { CA string `json:"tlscacert,omitempty"` Cert string `json:"tlscert,omitempty"` Key string `json:"tlskey,omitempty"` - VerifyRemote bool `json:"tlsverify"` - ManagerWhiteList string `json:"manager-whitelist"` + VerifyRemote bool `json:"tlsverify,omitempty"` + ManagerWhiteList string `json:"manager-whitelist,omitempty"` } // NewAPIClient initializes a new API client for the given host diff --git a/client/daemon_update.go b/client/daemon_update.go new file mode 100644 index 000000000..a58c4def2 --- /dev/null +++ b/client/daemon_update.go @@ -0,0 +1,18 @@ +package client + +import ( + "context" + + "github.com/alibaba/pouch/apis/types" +) + +// DaemonUpdate requests daemon to update daemon config. +func (client *APIClient) DaemonUpdate(ctx context.Context, config *types.DaemonUpdateConfig) error { + resp, err := client.post(ctx, "/daemon/update", nil, config, nil) + if err != nil { + return err + } + + ensureCloseReader(resp) + return nil +} diff --git a/client/daemon_update_test.go b/client/daemon_update_test.go new file mode 100644 index 000000000..71c0db34c --- /dev/null +++ b/client/daemon_update_test.go @@ -0,0 +1,51 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/alibaba/pouch/apis/types" +) + +func TestDaemonUpdateError(t *testing.T) { + client := &APIClient{ + HTTPCli: newMockClient(errorMockResponse(http.StatusInternalServerError, "Server error")), + } + err := client.DaemonUpdate(context.Background(), &types.DaemonUpdateConfig{}) + if err == nil || !strings.Contains(err.Error(), "Server error") { + t.Fatalf("expected a Server Error, got %v", err) + } +} + +func TestDaemonUpdate(t *testing.T) { + expectedURL := "/daemon/update" + + httpClient := newMockClient(func(req *http.Request) (*http.Response, error) { + if !strings.HasPrefix(req.URL.Path, expectedURL) { + return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL) + } + if req.Header.Get("Content-Type") == "application/json" { + var updateConfig interface{} + if err := json.NewDecoder(req.Body).Decode(&updateConfig); err != nil { + return nil, fmt.Errorf("failed to parse json: %v", err) + } + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }, nil + }) + client := &APIClient{ + HTTPCli: httpClient, + } + + if err := client.DaemonUpdate(context.Background(), &types.DaemonUpdateConfig{Labels: []string{"abc=def"}}); err != nil { + t.Fatal(err) + } +} diff --git a/client/interface.go b/client/interface.go index a56059dd6..c74d01b71 100644 --- a/client/interface.go +++ b/client/interface.go @@ -66,6 +66,7 @@ type SystemAPIClient interface { SystemVersion(ctx context.Context) (*types.SystemVersion, error) SystemInfo(ctx context.Context) (*types.SystemInfo, error) RegistryLogin(ctx context.Context, auth *types.AuthConfig) (*types.AuthResponse, error) + DaemonUpdate(ctx context.Context, daemonConfig *types.DaemonUpdateConfig) error } // NetworkAPIClient defines methods of Network client. diff --git a/cri/config/config.go b/cri/config/config.go index c5e5b231e..911f6a2ed 100644 --- a/cri/config/config.go +++ b/cri/config/config.go @@ -3,13 +3,13 @@ package config // Config defines the CRI configuration. type Config struct { // Listen is the listening address which servers CRI. - Listen string + Listen string `json:"listen,omitempty"` // NetworkPluginBinDir is the directory in which the binaries for the plugin is kept. - NetworkPluginBinDir string + NetworkPluginBinDir string `json:"network-plugin-bin-dir,omitempty"` // NetworkPluginConfDir is the directory in which the admin places a CNI conf. - NetworkPluginConfDir string + NetworkPluginConfDir string `json:"network-plugin-conf-dir,omitempty"` // SandboxImage is the image used by sandbox container. - SandboxImage string + SandboxImage string `json:"sandbox-image,omitempty"` // CriVersion is the cri version - CriVersion string + CriVersion string `json:"cri-version,omitempty"` } diff --git a/daemon/config/config.go b/daemon/config/config.go index 9da65799e..5c4b7bc8b 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -22,7 +22,7 @@ import ( // Config refers to daemon's whole configurations. type Config struct { - sync.Mutex + sync.Mutex `json:"-"` //Volume config VolumeConfig volume.Config `json:"volume-config,omitempty"` @@ -34,7 +34,7 @@ type Config struct { IsCriEnabled bool `json:"enable-cri,omitempty"` // CRI config. - CriConfig criconfig.Config + CriConfig criconfig.Config `json:"cri-config,omitempty"` // Server listening address. Listen []string `json:"listen,omitempty"` @@ -59,10 +59,10 @@ type Config struct { // ContainerdPath is the absolute path of containerd binary, // /usr/local/bin is the default. - ContainerdPath string `json:"containerd-path"` + ContainerdPath string `json:"containerd-path,omitempty"` // TLS configuration - TLS client.TLSConfig + TLS client.TLSConfig `json:"TLS,omitempty"` // Default OCI Runtime DefaultRuntime string `json:"default-runtime,omitempty"` @@ -101,10 +101,10 @@ type Config struct { Pidfile string `json:"pidfile,omitempty"` // Default log configuration - DefaultLogConfig types.LogConfig `json:"default-log-config, omitempty"` + DefaultLogConfig types.LogConfig `json:"default-log-config,omitempty"` // RegistryService - RegistryService types.RegistryServiceConfig `json:"registry-service, omitempty" ` + RegistryService types.RegistryServiceConfig `json:"registry-service,omitempty" ` // oom_score_adj for the daemon OOMScoreAdjust int `json:"oom-score-adjust,omitempty"` @@ -156,16 +156,16 @@ func (cfg *Config) MergeConfigurations(flagSet *pflag.FlagSet) error { return fmt.Errorf("failed to read contents from config file %s: %s", cfg.ConfigFile, err) } - var fileFlags map[string]interface{} - if err = json.NewDecoder(bytes.NewReader(contents)).Decode(&fileFlags); err != nil { + var origin map[string]interface{} + if err = json.NewDecoder(bytes.NewReader(contents)).Decode(&origin); err != nil { return fmt.Errorf("failed to decode json: %s", err) } - - if len(fileFlags) == 0 { + if len(origin) == 0 { return nil } - transferTLSConfig(fileFlags) + fileFlags := make(map[string]interface{}, 0) + iterateConfig(origin, fileFlags) // check if invalid or unknown flag exist in config file if err = getUnknownFlags(flagSet, fileFlags); err != nil { @@ -225,33 +225,10 @@ func (cfg *Config) delValue(flagSet *pflag.FlagSet, fileFlags map[string]interfa return cfg } -// transferTLSConfig fetch key value from tls config -// { -// "tlscert": "...", -// "tlscacert": "..." -// } -// add this transfer logic since no flag named TLS, but tlscert, tlscert... -// we should fetch them to do unknown flags and conflict flags check -func transferTLSConfig(config map[string]interface{}) { - v, exist := config["TLS"] - if !exist { - return - } - - var tlscofig map[string]interface{} - iterateConfig(map[string]interface{}{ - "TLS": v, - }, tlscofig) - - for k, v := range tlscofig { - config[k] = v - } -} - // iterateConfig resolves key-value from config file iteratly. func iterateConfig(origin map[string]interface{}, config map[string]interface{}) { for k, v := range origin { - if c, ok := v.(map[string]interface{}); ok { + if c, ok := v.(map[string]interface{}); ok && k != "add-runtime" { iterateConfig(c, config) } else { config[k] = v diff --git a/main.go b/main.go index 50cafc7bf..4a0c37571 100644 --- a/main.go +++ b/main.go @@ -95,6 +95,7 @@ func setupFlags(cmd *cobra.Command) { flagSet.StringVar(&cfg.VolumeConfig.DriverAlias, "volume-driver-alias", "", "Set volume driver alias, [;name1=alias1]") // network config + flagSet.StringVar(&cfg.NetworkConfig.ExecRoot, "exec-root-dir", "", "Set exec root directory for network") flagSet.StringVar(&cfg.NetworkConfig.BridgeConfig.Name, "bridge-name", "", "Set default bridge name") flagSet.StringVar(&cfg.NetworkConfig.BridgeConfig.IP, "bip", "", "Set bridge IP") flagSet.StringVar(&cfg.NetworkConfig.BridgeConfig.GatewayIPv4, "default-gateway", "", "Set default bridge gateway") diff --git a/network/config.go b/network/config.go index d35f15d32..c4fa4512d 100644 --- a/network/config.go +++ b/network/config.go @@ -7,30 +7,30 @@ var DefaultExecRoot = "/var/run/pouch" type Config struct { Type string `json:"-"` - MetaPath string `json:"meta-path"` // meta store - ExecRoot string `json:"exec-root-dir"` // exec root - DNS []string `json:"dns"` - DNSOptions []string `json:"dns-options"` - DNSSearch []string `json:"dns-search"` + MetaPath string `json:"meta-path,omitempty"` // meta store + ExecRoot string `json:"exec-root-dir,omitempty"` // exec root + DNS []string `json:"dns,omitempty"` + DNSOptions []string `json:"dns-options,omitempty"` + DNSSearch []string `json:"dns-search,omitempty"` // bridge config - BridgeConfig BridgeConfig `json:"bridge-config"` + BridgeConfig BridgeConfig `json:"bridge-config,omitempty"` ActiveSandboxes map[string]interface{} `json:"-"` } // BridgeConfig defines the bridge network configuration. type BridgeConfig struct { - Name string `json:"bridge-name"` - IP string `json:"bip"` - FixedCIDR string `json:"fixed-cidr"` - GatewayIPv4 string `json:"default-gateway"` - PreferredIP string `json:"preferred-ip"` + Name string `json:"bridge-name,omitempty"` + IP string `json:"bip,omitempty"` + FixedCIDR string `json:"fixed-cidr,omitempty"` + GatewayIPv4 string `json:"default-gateway,omitempty"` + PreferredIP string `json:"preferred-ip,omitempty"` - Mtu int `json:"mtu"` - ICC bool `json:"icc"` + Mtu int `json:"mtu,omitempty"` + ICC bool `json:"icc,omitempty"` IPTables bool `json:"iptables"` IPForward bool `json:"ipforward"` - IPMasq bool `json:"ipmasq"` + IPMasq bool `json:"ipmasq,omitempty"` UserlandProxy bool `json:"userland-proxy"` } diff --git a/network/mode/bridge/bridge.go b/network/mode/bridge/bridge.go index 738c86026..49f05da56 100644 --- a/network/mode/bridge/bridge.go +++ b/network/mode/bridge/bridge.go @@ -119,6 +119,11 @@ func New(ctx context.Context, config network.BridgeConfig, manager mgr.NetworkMg Config: []types.IPAMConfig{ipamV4Conf}, } + mtu := 1500 + if config.Mtu != 0 { + mtu = config.Mtu + } + networkCreate := types.NetworkCreate{ Driver: "bridge", EnableIPV6: false, @@ -126,7 +131,7 @@ func New(ctx context.Context, config network.BridgeConfig, manager mgr.NetworkMg Options: map[string]string{ bridge.BridgeName: bridgeName, bridge.DefaultBridge: strconv.FormatBool(true), - netlabel.DriverMTU: strconv.Itoa(config.Mtu), + netlabel.DriverMTU: strconv.Itoa(mtu), bridge.EnableICC: strconv.FormatBool(true), bridge.DefaultBindingIP: DefaultBindingIP, bridge.EnableIPMasquerade: strconv.FormatBool(false), diff --git a/storage/volume/config.go b/storage/volume/config.go index 1f606ae94..18cd56e95 100644 --- a/storage/volume/config.go +++ b/storage/volume/config.go @@ -6,9 +6,9 @@ import ( // Config represents volume config struct. type Config struct { - Timeout time.Duration `json:"volume-timeout"` // operation timeout. - RemoveVolume bool `json:"remove-volume"` // remove volume add data or volume's metadata when remove pouch volume. - DefaultBackend string `json:"volume-default-driver"` // default volume backend. - VolumeMetaPath string `json:"volume-meta-dir"` // volume metadata store path. - DriverAlias string `json:"volume-driver-alias"` // driver alias configure. + Timeout time.Duration `json:"volume-timeout,omitempty"` // operation timeout. + RemoveVolume bool `json:"remove-volume,omitempty"` // remove volume add data or volume's metadata when remove pouch volume. + DefaultBackend string `json:"volume-default-driver,omitempty"` // default volume backend. + VolumeMetaPath string `json:"volume-meta-dir,omitempty"` // volume metadata store path. + DriverAlias string `json:"volume-driver-alias,omitempty"` // driver alias configure. } diff --git a/test/z_cli_daemon_test.go b/test/z_cli_daemon_test.go index 22e7b965e..ee6b6e2a7 100644 --- a/test/z_cli_daemon_test.go +++ b/test/z_cli_daemon_test.go @@ -466,3 +466,19 @@ func (suite *PouchDaemonSuite) TestDaemonWithMultiRuntimes(c *check.C) { c.Assert(err, check.NotNil) dcfg2.KillDaemon() } + +func (suite *PouchDaemonSuite) TestUpdateDaemonWithLabels(c *check.C) { + cfg := daemon.NewConfig() + err := cfg.StartDaemon() + c.Assert(err, check.IsNil) + + defer cfg.KillDaemon() + + RunWithSpecifiedDaemon(&cfg, "updatedaemon", "--label", "aaa=bbb").Assert(c, icmd.Success) + + ret := RunWithSpecifiedDaemon(&cfg, "info") + ret.Assert(c, icmd.Success) + + updated := strings.Contains(ret.Stdout(), "aaa=bbb") + c.Assert(updated, check.Equals, true) +}