diff --git a/internal/config/config.go b/internal/config/config.go index 953d9b670..76133a2aa 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -82,8 +82,6 @@ type FolderConfiguration struct { IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"` Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved - - deviceIDs []protocol.DeviceID } func (f FolderConfiguration) Copy() FolderConfiguration { @@ -144,12 +142,11 @@ func (f *FolderConfiguration) HasMarker() bool { } func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID { - if f.deviceIDs == nil { - for _, n := range f.Devices { - f.deviceIDs = append(f.deviceIDs, n.DeviceID) - } + deviceIDs := make([]protocol.DeviceID, len(f.Devices)) + for i, n := range f.Devices { + deviceIDs[i] = n.DeviceID } - return f.deviceIDs + return deviceIDs } type VersioningConfiguration struct { diff --git a/internal/model/model.go b/internal/model/model.go index 21e20c226..a4ca60f0a 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -691,14 +691,7 @@ func (m *Model) Close(device protocol.DeviceID, err error) { conn, ok := m.rawConn[device] if ok { - if conn, ok := conn.(*tls.Conn); ok { - // If the underlying connection is a *tls.Conn, Close() does more - // than it says on the tin. Specifically, it sends a TLS alert - // message, which might block forever if the connection is dead - // and we don't have a deadline site. - conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond)) - } - conn.Close() + closeRawConn(conn) } delete(m.protoConn, device) delete(m.rawConn, device) @@ -1732,30 +1725,132 @@ func (m *Model) VerifyConfiguration(from, to config.Configuration) error { func (m *Model) CommitConfiguration(from, to config.Configuration) bool { // TODO: This should not use reflect, and should take more care to try to handle stuff without restart. - // Adding, removing or changing folders requires restart - if !reflect.DeepEqual(from.Folders, to.Folders) { - return false + // Go through the folder configs and figure out if we need to restart or not. + + fromFolders := mapFolders(from.Folders) + toFolders := mapFolders(to.Folders) + for folderID := range toFolders { + if _, ok := fromFolders[folderID]; !ok { + // A folder was added. Requires restart. + if debug { + l.Debugln(m, "requires restart, adding folder", folderID) + } + return false + } + } + + for folderID, fromCfg := range fromFolders { + toCfg, ok := toFolders[folderID] + if !ok { + // A folder was removed. Requires restart. + if debug { + l.Debugln(m, "requires restart, removing folder", folderID) + } + return false + } + + // This folder exists on both sides. Compare the device lists, as we + // can handle adding a device (but not currently removing one). + + fromDevs := mapDevices(fromCfg.DeviceIDs()) + toDevs := mapDevices(toCfg.DeviceIDs()) + for dev := range fromDevs { + if _, ok := toDevs[dev]; !ok { + // A device was removed. Requires restart. + if debug { + l.Debugln(m, "requires restart, removing device", dev, "from folder", folderID) + } + return false + } + } + + for dev := range toDevs { + if _, ok := fromDevs[dev]; !ok { + // A device was added. Handle it! + + m.fmut.Lock() + m.pmut.Lock() + + m.folderCfgs[folderID] = toCfg + m.folderDevices[folderID] = append(m.folderDevices[folderID], dev) + m.deviceFolders[dev] = append(m.deviceFolders[dev], folderID) + + // If we already have a connection to this device, we should + // disconnect it so that we start sharing the folder with it. + // We close the underlying connection and let the normal error + // handling kick in to clean up and reconnect. + if conn, ok := m.rawConn[dev]; ok { + closeRawConn(conn) + } + + m.pmut.Unlock() + m.fmut.Unlock() + } + } + + // Check if anything else differs, apart from the device list. + fromCfg.Devices = nil + toCfg.Devices = nil + if !reflect.DeepEqual(fromCfg, toCfg) { + if debug { + l.Debugln(m, "requires restart, folder", folderID, "configuration differs") + } + return false + } } // Removing a device requres restart - toDevs := make(map[protocol.DeviceID]bool, len(from.Devices)) - for _, dev := range to.Devices { - toDevs[dev.DeviceID] = true - } + toDevs := mapDeviceCfgs(from.Devices) for _, dev := range from.Devices { if _, ok := toDevs[dev.DeviceID]; !ok { + if debug { + l.Debugln(m, "requires restart, device", dev.DeviceID, "was removed") + } return false } } // All of the generic options require restart if !reflect.DeepEqual(from.Options, to.Options) { + if debug { + l.Debugln(m, "requires restart, options differ") + } return false } return true } +// mapFolders returns a map of folder ID to folder configuration for the given +// slice of folder configurations. +func mapFolders(folders []config.FolderConfiguration) map[string]config.FolderConfiguration { + m := make(map[string]config.FolderConfiguration, len(folders)) + for _, cfg := range folders { + m[cfg.ID] = cfg + } + return m +} + +// mapDevices returns a map of device ID to nothing for the given slice of +// device IDs. +func mapDevices(devices []protocol.DeviceID) map[protocol.DeviceID]struct{} { + m := make(map[protocol.DeviceID]struct{}, len(devices)) + for _, dev := range devices { + m[dev] = struct{}{} + } + return m +} + +// mapDeviceCfgs returns a map of device ID to nothing for the given slice of +// device configurations. +func mapDeviceCfgs(devices []config.DeviceConfiguration) map[protocol.DeviceID]struct{} { + m := make(map[protocol.DeviceID]struct{}, len(devices)) + for _, dev := range devices { + m[dev.DeviceID] = struct{}{} + } + return m +} + func filterIndex(folder string, fs []protocol.FileInfo, dropDeletes bool) []protocol.FileInfo { for i := 0; i < len(fs); { if fs[i].Flags&^protocol.FlagsAll != 0 { @@ -1816,3 +1911,14 @@ func getChunk(data []string, skip, get int) ([]string, int, int) { } return data[skip : skip+get], 0, 0 } + +func closeRawConn(conn io.Closer) error { + if conn, ok := conn.(*tls.Conn); ok { + // If the underlying connection is a *tls.Conn, Close() does more + // than it says on the tin. Specifically, it sends a TLS alert + // message, which might block forever if the connection is dead + // and we don't have a deadline set. + conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond)) + } + return conn.Close() +} diff --git a/internal/rc/rc.go b/internal/rc/rc.go index 634f96de0..793b6f032 100644 --- a/internal/rc/rc.go +++ b/internal/rc/rc.go @@ -63,6 +63,10 @@ func NewProcess(addr string) *Process { return p } +func (p *Process) ID() protocol.DeviceID { + return p.id +} + // LogTo creates the specified log file and ensures that stdout and stderr // from the Start()ed process is redirected there. Must be called before // Start(). @@ -229,6 +233,34 @@ func (p *Process) RescanDelay(folder string, delaySeconds int) error { return err } +func (p *Process) ConfigInSync() (bool, error) { + bs, err := p.Get("/rest/system/config/insync") + if err != nil { + return false, err + } + return bytes.Contains(bs, []byte("true")), nil +} + +func (p *Process) GetConfig() (config.Configuration, error) { + var cfg config.Configuration + bs, err := p.Get("/rest/system/config") + if err != nil { + return cfg, err + } + + err = json.Unmarshal(bs, &cfg) + return cfg, err +} + +func (p *Process) PostConfig(cfg config.Configuration) error { + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(cfg); err != nil { + return err + } + _, err := p.Post("/rest/system/config", buf) + return err +} + func InSync(folder string, ps ...*Process) bool { for _, p := range ps { p.eventMut.Lock() diff --git a/test/.gitignore b/test/.gitignore index 8127f40cc..85a787119 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,6 +1,7 @@ s1 s2 s3 +s4 s12-1 s12-2 s23-2 @@ -20,3 +21,4 @@ h*/index* panic-*.log audit-*.log h*/config.xml.v* +h*/config.xml.orig diff --git a/test/h1/config.xml b/test/h1/config.xml index 732b2f735..e0ae9d398 100644 --- a/test/h1/config.xml +++ b/test/h1/config.xml @@ -3,6 +3,7 @@ + false 1 @@ -31,6 +32,9 @@
127.0.0.1:22003
+ +
127.0.0.1:22004
+
127.0.0.1:8081
testuser diff --git a/test/h4/cert.pem b/test/h4/cert.pem new file mode 100644 index 000000000..d329435cb --- /dev/null +++ b/test/h4/cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID6TCCAlGgAwIBAgIISz5XufRr9xMwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UE +AxMJc3luY3RoaW5nMB4XDTE1MDcyMjA3MDIzOFoXDTQ5MTIzMTIzNTk1OVowFDES +MBAGA1UEAxMJc3luY3RoaW5nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKC +AYEA4nE2FPVQkfMStJms0SUEjSi5qUC4I2+aCFD+q6rLJHhgzdvjXoQ8iWX8hFLu +nza3mMKTSjcThnpR/yA1S0ipATsdQ5c5xjceliSLDxImBcBaMtvGejgOlFwC6zTz +5CJAnLo8odQtAgaaUtGJU145OAHM/cTA0xKd+nh0UvuJHT56Ur6dZ/VKzONnWsUW +qI/YVp7mRvv1PimN74ppTQSadU1s3gyq3b7mnl/aWjN42/G6kO27NXA1lVblnFk/ +Cee6HFxUIy5upTFXnAm1DaEFVdzQ1dxBAEXwIbh2WOXeVCyDONzaqVcYPYQKG5NT +KbYY08rnDmRFlURHFQ/eEr49zniLrQRfL3pSNCEGmuVpPAEsuGQ5EQW1b8aEFMgp +IR+Jo59JyU04HrP27VctyUEBT4MCQn4G9gN6Qy1EKTKq49UVNR+1eMtuq9/o6tXl +rwepnO9AITclPdpvGc93hTshEBZFQF+rHkUMoj7jXr9zAGchRoY8cxaJM0DGrpjc +uGONAgMBAAGjPzA9MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD +AQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAYEAgiC2 +LYPXPCtuaF7qGbas0A5zYtPr0PrXaILl4uYA63+ZXKPMOQ+LkdgRzSQxvKLrPLQM +/LwWOTONuqT2sw8Wj+MilzDOXIlEWG2Gqy3/xS7H5RAkZqjVHhuBRXnJiZEl5HAh +ASMGiyejII2uN7k+5sjCFmuSfdcI18f/AjUL5fz53TpIJinyCakQipdicI9jZvLR +jJ2sqy9wJ3yhTtUm5M33bsLPjhnwMkTTYvvMomfRI8qUYflWxb5BZ82FvNVUE9kA +hDdJzluINMofMAblyf9TxX0q1bunPc9soAMtUSDWRmNtviV9uggEdtGYrmDrK7Dz ++89AB60QSN6MJzVNPdJZCPvefuJjk9isQBUbQE/CsVFeooKJ/DU5arbUV2mjaifV +Z6GxHiEkynSWaNMQLioi+vPguMdAuotdqpInVjCLKJbKiOXrYfIhYJFATc0lRBHx +9LUH020HOACgX+WVFiDEDx7OCu868IbDJK/gryb5IfIpbaY4xit9eoqMS4BP +-----END CERTIFICATE----- diff --git a/test/h4/config.xml b/test/h4/config.xml new file mode 100644 index 000000000..2921c5926 --- /dev/null +++ b/test/h4/config.xml @@ -0,0 +1,47 @@ + + + + + 1 + 16 + 0 + random + false + + +
dynamic
+
+ +
127.0.0.1:8084
+ PMA5yUTG-Mw98nJ0YEtWTCHlM5O4aNi0 +
+ + 127.0.0.1:22004 + udp4://announce.syncthing.net:22026 + udp6://announce-v6.syncthing.net:22026 + false + false + 21025 + [ff32::5222]:21026 + 0 + 0 + 60 + false + false + 60 + 30 + 10 + -1 + + true + 12 + 24 + true + 5 + true + false + 0 + 30 + 60 + +
diff --git a/test/h4/https-cert.pem b/test/h4/https-cert.pem new file mode 100644 index 000000000..e767708eb --- /dev/null +++ b/test/h4/https-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3zCCAkegAwIBAgIIWH6f9/hiHaowDQYJKoZIhvcNAQELBQAwDzENMAsGA1UE +AxMEc3lubzAeFw0xNTA3MjIwNzE1MjlaFw00OTEyMzEyMzU5NTlaMA8xDTALBgNV +BAMTBHN5bm8wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCgideynuoI +MfN2PR7WfPWvRjnYNuNp5U1C5GzAfrKxVaHkfpt+AsXHHsuo1Xl3gdsIs1Uc2Z8R +yLPxFgT+bLKKqwTw4D/9JTHtF2vOLkZLB4/0Bhe2BAXepEEIZDqEHsNE7A8ma9Jv +JlBxW55xoXUE5ak2tNvxQneoDj+WKpd24jyZBMp/TC52dhy6TmDfahrQjU29Nz7n +tlVC1eol7YqB7+M1CXK2OK74m9J9G8tnweDKJKPv9t011dIhyd2GqRI36fU1EuIC ++NSWhcl1VGEa3eCN9Bn+pUo5oDSiMfGmbVo77al31wpN+2+BprH/JTWSWtvBG6uh +Cyq5cqkDxMeXmCD863+xorE0hyqZkRrS2XSaJI7hhOgVCUUrfPMK3p9n1pkZ+RfN +AtYFPhit2bJyjSBJNN0qxnmMHspZFO+eoeNQkaeL7sDeHLo2ZEUIJMyq4ElsimLU +i/+bQCaHl4vz/rz8nRNnIsm4o2adgLie3ZA2lJ+5vEBN+1GlaHIrEnUCAwEAAaM/ +MD0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBgQBsbVHPEvzX2Emas+yG +zbKa1wcuxNWn7nmYjz8YXuFURjGAt1U8wPV+YhgZrhR1rImwGRkXjRwL3vCvm5xi +aTNNK2g132amMKhWcAwm/bXJsW3smFpUmmb6j1jZj2eQo3UFNpEql+GzHF/iLWgA +74xsqRkqTR/tkoD/W47ASn92rlj8vKmVafiq132/YlqxzaJB4FQyfmdHd1HMsStk +r531DXSBsK9CBnM/oEkoCBsJFi6xiUNf7D7wjvoVnCcrIx4bNXiMKgbZA/M0mh9t +bDI5b+2j1Af7npPzHAEYEWbWSGwpDBnpB9PuG11WjozLpwDA2My5yjiwHQYw3cIV +QM17Oia97QjgOLbbG5Hpy6SF0KxUyCINpg780U7WKyVLherpdQ1ABRmlC0laXDh5 +Oq500d316ej28VITWj3gMhocw4KwXpkjh9cweLTPV7wiUsoO2ksEMjEPdGCjzHXg +k7KQB7dqbOS7VIOJj8+GPbaf3aTdG+b1z3KVcDMH+59TddE= +-----END CERTIFICATE----- diff --git a/test/h4/https-key.pem b/test/h4/https-key.pem new file mode 100644 index 000000000..977d43a0c --- /dev/null +++ b/test/h4/https-key.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/test/h4/key.pem b/test/h4/key.pem new file mode 100644 index 000000000..9b2be1337 --- /dev/null +++ b/test/h4/key.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG4wIBAAKCAYEA4nE2FPVQkfMStJms0SUEjSi5qUC4I2+aCFD+q6rLJHhgzdvj +XoQ8iWX8hFLunza3mMKTSjcThnpR/yA1S0ipATsdQ5c5xjceliSLDxImBcBaMtvG +ejgOlFwC6zTz5CJAnLo8odQtAgaaUtGJU145OAHM/cTA0xKd+nh0UvuJHT56Ur6d +Z/VKzONnWsUWqI/YVp7mRvv1PimN74ppTQSadU1s3gyq3b7mnl/aWjN42/G6kO27 +NXA1lVblnFk/Cee6HFxUIy5upTFXnAm1DaEFVdzQ1dxBAEXwIbh2WOXeVCyDONza +qVcYPYQKG5NTKbYY08rnDmRFlURHFQ/eEr49zniLrQRfL3pSNCEGmuVpPAEsuGQ5 +EQW1b8aEFMgpIR+Jo59JyU04HrP27VctyUEBT4MCQn4G9gN6Qy1EKTKq49UVNR+1 +eMtuq9/o6tXlrwepnO9AITclPdpvGc93hTshEBZFQF+rHkUMoj7jXr9zAGchRoY8 +cxaJM0DGrpjcuGONAgMBAAECggGAf0LhAiZcgan6eUVkuqXzSOH6dgTJeCDgkIv0 +lMYIJRcCUK+juRrYat/GaxewxAocZN31qWAKuSlFq/yN9yF+2hI/AB2deqi/p+Ih +xPaOJ+1SxAKAKXAXwYl0mnvIFg6qAWspaEm2gcz0LldUtmXeAnwAmR5awEVWQ84u +kfSLusPCO36lOCfDQiMLkxfxBArTqtri0EIKMkVoX5eKVp6fsA0zghfcb4M6WQfF +z6vd4L6Z+5mf/QhzFNshcB04MHjqMNRY5WQATZiT1KW3z1kzUWe5eWWSxYQHOVEu +VOZuMpsq2TuwBEJDEzzJoOVXRzx6AfSNdyrGYEkq05h/vxwQZTIdZKs97s1nwzu2 +pkltY1Pf3BdjAvfpCAkxmdu8l+fjlMmgav/lT2O4ZHTbu1MaqNLN5QLGiLxS0I6f +gdS9iNgYMVwfpGi7UVNLqrG2nxQmYB0LQyZqNFa+9wNbUzN3h3Qq6eKTXl0uBP5C +PdMUdJ3pF7iJM8tshcTb9ALBU/wBAoHBAOVpjYyXtNQOBm/aFkTcJB81AeBgCk3f +lxWAs+GwprlPnwCZdH5CvYD2ULGChwaGoYXItRFbWUyP6Tl9c7g3c/MRZJ1M78P9 +VXA30KPKm9T2dT5ZDCJSUnkZPYP0EagfpYJ9dRxK/55uZ+IGtbXcjkPAVe3wzZfZ +8aWgg9w/qmqgiJ8PUmsYY6lILokj2uanmYo410e3RPvN1+qei78RBW6XAh8yJJNL +R7vjpjcxtWvY/PKt8b2Yo+DpnHYWm/AqyQKBwQD8r4npkjzrbrS5BQ1mkNDnSaax +p7j0hWW+bN0c/EHial2ESKw6vnN/Y+6WcdMq1SgiwkFA7OpthHhY/bUkuyu2BQyo +dLFL05KOusS98YTOdMlho4lGJS4HdCi9qeYuroS8gjYsOmDf2PdM1bHKjzkGG0JL +igewb6AaF6Yp46jbzqz+PE6WbTTdkfdWXtmDIRTTTOfY7ovG0xLAaYJqNEOzbnQT +Emj0ggYNaokGfO6uOk6okuRP7VLaVnxXbvoeUKUCgcBIbORlKFfMQolBsqYpIx68 +Q23OOkPGhfoarcEcVTqtcjeOZuPiIIvXNOwQvlaGduZzaAPR8PbmNuC4Z6Sq2cbf +S/RpvKpNQ6M/hD94FjTQLOaiwlYUV8z1skQ7bkhMvYDxC053mi3NBKoDL38aZQD8 +3rHCJq2hbQre8Sfv1qGke/3lyV6JtO9xt/oJDarD+tF8U6mTWIaMwFWUGm2f6m2+ +linzU088uR1ycdI9xpGx9JUWwFd7Nb82+EmO9mBQmBECgcEAqHubJ2RcvlZ4pg1a +XBMfV7hiL3638kKoDoqj/FmuzHtDk5qpTBoFBOHrCeEnfh3WvyZrQBE4VoHHhP7V +s4IhqSJAyGnWdcrCo+yglk3d0ZNJW5MhSuYrhMjNCXmpg2LWGqNv35mlUlxmuJKc +E4Xf7dRrJdcJPXmQdRVjs/aadsWdz38Cn4Z9g2d6Vdq0iZybODDFPn4AMTg3/pfb +X1kt8wwo1TanSLERvAxXBT50HzO9kuUu2qRRZEfabKoQl/oJAoHAehR8ULlvRKFi +ZAW/uzKT3CLEa0z8JDdQTEsfxAfaeJ/EjMHgxdni5b45c80MBJbmJyetiMg5tJxM +wGKmmux/PuDjg5YEdvJLjIZvBrGlZvLlSw9US13zn+RKglKveGBOxc4qx56AJn1Z +GLcpjdNq4kmbXq5DtSig+jpqnfAU9bKF9duOSxKoYQv9NapndI900ozW98+1SWiC +bxvuPS5n7boLfwLlmvIhX7L/V7iLc5rCAI+0b08JsmMmIDOlytJW +-----END RSA PRIVATE KEY----- diff --git a/test/norestart_test.go b/test/norestart_test.go new file mode 100644 index 000000000..67ac27a50 --- /dev/null +++ b/test/norestart_test.go @@ -0,0 +1,85 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build integration + +package integration + +import ( + "log" + "os" + "testing" + + "github.com/syncthing/protocol" + "github.com/syncthing/syncthing/internal/config" + "github.com/syncthing/syncthing/internal/rc" +) + +func TestAddDeviceWithoutRestart(t *testing.T) { + log.Println("Cleaning...") + err := removeAll("s1", "h1/index*", "s4", "h4/index*") + if err != nil { + t.Fatal(err) + } + + log.Println("Generating files...") + err = generateFiles("s1", 100, 18, "../LICENSE") + if err != nil { + t.Fatal(err) + } + + p1 := startInstance(t, 1) + defer checkedStop(t, p1) + + p4 := startInstance(t, 4) + defer checkedStop(t, p4) + + if ok, err := p1.ConfigInSync(); err != nil || !ok { + t.Fatal("p1 should be in sync;", ok, err) + } + if ok, err := p4.ConfigInSync(); err != nil || !ok { + t.Fatal("p4 should be in sync;", ok, err) + } + + // Add the p1 device to p4. Back up and restore p4's config first. + + log.Println("Adding p1 to p4...") + + os.Remove("h4/config.xml.orig") + os.Rename("h4/config.xml", "h4/config.xml.orig") + defer os.Rename("h4/config.xml.orig", "h4/config.xml") + + cfg, err := p4.GetConfig() + if err != nil { + t.Fatal(err) + } + + devCfg := config.DeviceConfiguration{ + DeviceID: p1.ID(), + Name: "s1", + Addresses: []string{"127.0.0.1:22001"}, + Compression: protocol.CompressMetadata, + } + cfg.Devices = append(cfg.Devices, devCfg) + + cfg.Folders[0].Devices = append(cfg.Folders[0].Devices, config.FolderDeviceConfiguration{DeviceID: p1.ID()}) + + if err = p4.PostConfig(cfg); err != nil { + t.Fatal(err) + } + + // The change should not require a restart, so the config should be "in sync" + + if ok, err := p4.ConfigInSync(); err != nil || !ok { + t.Fatal("p4 should be in sync;", ok, err) + } + + // Wait for the devices to connect and sync. + + log.Println("Waiting for p1 and p4 to connect and sync...") + + rc.AwaitSync("default", p1, p4) +}