diff --git a/client/start_container_test.go b/client/start_container_test.go index e47817b..fff209a 100644 --- a/client/start_container_test.go +++ b/client/start_container_test.go @@ -25,6 +25,10 @@ import ( "google.golang.org/grpc/status" ) +const ( + locationLabel = "com.google.containerz.location" +) + type fakeStartingContainerzServer struct { fakeContainerzServer @@ -405,7 +409,8 @@ func TestStart(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", InstanceName: "some-instance", - Labels: map[string]string{"key1": "value1"}, + Labels: map[string]string{"key1": "value1", + locationLabel: cpb.StartContainerRequest_L_UNKNOWN.String()}, Limits: &cpb.StartContainerRequest_Limits{ MaxCpu: 1.0, SoftMemBytes: 1000, diff --git a/server/deploy_test.go b/server/deploy_test.go index f437229..4c35c8c 100644 --- a/server/deploy_test.go +++ b/server/deploy_test.go @@ -153,6 +153,7 @@ func (f *fakeContainerManager) ContainerUpdate(_ context.Context, instance, imag f.Envs = optionz.EnvMapping f.Volumes = optionz.Volumes f.Devices = optionz.Devices + f.Labels = optionz.Labels f.Network = optionz.Network f.Capabilities = optionz.Capabilities f.RunAs = optionz.RunAs diff --git a/server/start_container.go b/server/start_container.go index e7e3bd3..92ca152 100644 --- a/server/start_container.go +++ b/server/start_container.go @@ -19,6 +19,13 @@ import ( options "github.com/openconfig/containerz/containers" cpb "github.com/openconfig/gnoi/containerz" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + locationLabel = "com.google.containerz.location" ) // StartContainer starts a container. If the image does not exist on the target, @@ -27,7 +34,10 @@ import ( // should provide one. If the instance name already exists, the target should // return an error. func (s *Server) StartContainer(ctx context.Context, request *cpb.StartContainerRequest) (*cpb.StartContainerResponse, error) { - opts := optionsFromStartContainerRequest(request) + opts, err := optionsFromStartContainerRequest(request) + if err != nil { + return nil, err + } resp, err := s.mgr.ContainerStart(ctx, request.GetImageName(), request.GetTag(), request.GetCmd(), opts...) if err != nil { return nil, err @@ -42,7 +52,7 @@ func (s *Server) StartContainer(ctx context.Context, request *cpb.StartContainer }, nil } -func optionsFromStartContainerRequest(request *cpb.StartContainerRequest) []options.Option { +func optionsFromStartContainerRequest(request *cpb.StartContainerRequest) ([]options.Option, error) { var opts []options.Option if len(request.GetPorts()) != 0 { ports := make(map[uint32]uint32, len(request.GetPorts())) @@ -75,6 +85,35 @@ func optionsFromStartContainerRequest(request *cpb.StartContainerRequest) []opti } } - opts = append(opts, options.WithLabels(request.GetLabels()), options.WithEnv(request.GetEnvironment()), options.WithInstanceName(request.GetInstanceName()), options.WithVolumes(request.GetVolumes()), options.WithDevices(request.GetDevices())) - return opts + labels, err := labelsWithLocation(request) + if err != nil { + return nil, err + } + + opts = append(opts, options.WithLabels(labels), options.WithEnv(request.GetEnvironment()), options.WithInstanceName(request.GetInstanceName()), options.WithVolumes(request.GetVolumes()), options.WithDevices(request.GetDevices())) + return opts, nil +} + +// labelsWithLocation updates the labels map to include the location, based on the location +// field in the request. L_UNKNOWN is treated as L_PRIMARY +func labelsWithLocation(request *cpb.StartContainerRequest) (map[string]string, error) { + location := request.GetLocation() + if location == cpb.StartContainerRequest_L_UNKNOWN { + location = cpb.StartContainerRequest_L_PRIMARY + } + locationStr := cpb.StartContainerRequest_Location_name[int32(location)] + labels := request.GetLabels() + // if the label is already set but has the same value as the location, then ignore it. + if requestedLocation, ok := labels[locationLabel]; ok && requestedLocation != locationStr { + return nil, status.Errorf(codes.InvalidArgument, + "%q label (currently set to %q) should be not be set, or should match"+ + " location field %q. Unspecified location field is treated as L_PRIMARY", + locationLabel, requestedLocation, locationStr) + } else if !ok { + if labels == nil { + labels = make(map[string]string) + } + labels[locationLabel] = locationStr + } + return labels, nil } diff --git a/server/start_container_test.go b/server/start_container_test.go index 1f6580a..458cc92 100644 --- a/server/start_container_test.go +++ b/server/start_container_test.go @@ -16,12 +16,16 @@ package server import ( "context" + "errors" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + cpb "github.com/openconfig/gnoi/containerz" ) @@ -32,6 +36,7 @@ func TestContainerStart(t *testing.T) { inOpts []Option wantResp *cpb.StartContainerResponse wantState *fakeContainerManager + wantErr error }{ { name: "simple", @@ -39,6 +44,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_PRIMARY, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -46,6 +52,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -57,6 +65,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_PRIMARY, Ports: []*cpb.StartContainerRequest_Port{ { Internal: 1, @@ -74,6 +83,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -86,6 +97,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_PRIMARY, Ports: []*cpb.StartContainerRequest_Port{ { Internal: 1, @@ -105,6 +117,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -119,6 +133,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Environment: map[string]string{"1": "2", "3": "4"}, }, wantResp: &cpb.StartContainerResponse{ @@ -127,6 +142,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -139,6 +156,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_BACKUP, Volumes: []*cpb.Volume{ { Name: "vol1", @@ -160,6 +178,8 @@ func TestContainerStart(t *testing.T) { Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_BACKUP.String()}, Volumes: []*cpb.Volume{ { Name: "vol1", @@ -180,6 +200,7 @@ func TestContainerStart(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Network: "some-network", + Location: cpb.StartContainerRequest_L_PRIMARY, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -187,6 +208,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -203,6 +226,7 @@ func TestContainerStart(t *testing.T) { Add: []string{"cap1", "cap2"}, Remove: []string{"cap3", "cap4"}, }, + Location: cpb.StartContainerRequest_L_ALL, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -210,6 +234,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -225,6 +251,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Restart: &cpb.StartContainerRequest_Restart{ Policy: cpb.StartContainerRequest_Restart_ON_FAILURE, Attempts: 3, @@ -236,6 +263,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -251,6 +280,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, RunAs: &cpb.StartContainerRequest_RunAs{ User: "some-user", Group: "some-group", @@ -262,6 +292,8 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -278,6 +310,7 @@ func TestContainerStart(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Labels: map[string]string{"key1": "value1"}, + Location: cpb.StartContainerRequest_L_ALL, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -285,10 +318,11 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ - Image: "some-image", - Tag: "some-tag", - Cmd: "some-cmd", - Labels: map[string]string{"key1": "value1"}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{"key1": "value1", + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -302,6 +336,7 @@ func TestContainerStart(t *testing.T) { SoftMemBytes: 1000, HardMemBytes: 2000, }, + Location: cpb.StartContainerRequest_L_ALL, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -309,6 +344,7 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ + Labels: map[string]string{locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", @@ -323,6 +359,7 @@ func TestContainerStart(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Labels: map[string]string{locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Devices: []*cpb.Device{ { SrcPath: "dev1", @@ -340,6 +377,7 @@ func TestContainerStart(t *testing.T) { Permissions: []cpb.Device_Permission{cpb.Device_READ, cpb.Device_WRITE}, }, }, + Location: cpb.StartContainerRequest_L_ALL, }, wantResp: &cpb.StartContainerResponse{ Response: &cpb.StartContainerResponse_StartOk{ @@ -347,9 +385,10 @@ func TestContainerStart(t *testing.T) { }, }, wantState: &fakeContainerManager{ - Image: "some-image", - Tag: "some-tag", - Cmd: "some-cmd", + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Devices: []*cpb.Device{ { SrcPath: "dev1", @@ -369,6 +408,171 @@ func TestContainerStart(t *testing.T) { }, }, }, + { + name: "location-unknown-set-in-map", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_UNKNOWN.String(), + }, + }, + wantErr: status.Errorf(codes.InvalidArgument, + "%q label (currently set to %q) should be not be set, or should match"+ + " location field %q. Unspecified location field is treated as L_PRIMARY", + locationLabel, cpb.StartContainerRequest_L_UNKNOWN.String(), + cpb.StartContainerRequest_L_PRIMARY.String()), + wantState: &fakeContainerManager{}, + }, + { + name: "non-location-label-set", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{"foo": "bar"}, + }, + wantResp: &cpb.StartContainerResponse{ + Response: &cpb.StartContainerResponse_StartOk{ + StartOk: &cpb.StartOK{}, + }, + }, + wantState: &fakeContainerManager{ + Labels: map[string]string{ + "foo": "bar", + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + }, + { + name: "location-unknown-treated-as-primary", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + wantResp: &cpb.StartContainerResponse{ + Response: &cpb.StartContainerResponse_StartOk{ + StartOk: &cpb.StartOK{}, + }, + }, + wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + }, + { + name: "mismatching-location", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String(), + }, + Location: cpb.StartContainerRequest_L_BACKUP, + }, + wantState: &fakeContainerManager{}, + wantErr: status.Errorf(codes.InvalidArgument, + "%q label (currently set to %q) should be not be set, or should match"+ + " location field %q. Unspecified location field is treated as L_PRIMARY", + locationLabel, cpb.StartContainerRequest_L_ALL.String(), + cpb.StartContainerRequest_L_BACKUP.String()), + }, + { + name: "location-label-only", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String(), + }, + }, + wantState: &fakeContainerManager{}, + wantErr: status.Errorf(codes.InvalidArgument, + "%q label (currently set to %q) should be not be set, or should match"+ + " location field %q. Unspecified location field is treated as L_PRIMARY", + locationLabel, cpb.StartContainerRequest_L_ALL.String(), + cpb.StartContainerRequest_L_PRIMARY.String()), + }, + { + name: "matching-location", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String(), + }, + Location: cpb.StartContainerRequest_L_PRIMARY, + }, + wantResp: &cpb.StartContainerResponse{ + Response: &cpb.StartContainerResponse_StartOk{ + StartOk: &cpb.StartOK{}, + }, + }, + wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + }, + { + name: "location-field-only", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_PRIMARY, + }, + wantResp: &cpb.StartContainerResponse{ + Response: &cpb.StartContainerResponse_StartOk{ + StartOk: &cpb.StartOK{}, + }, + }, + wantState: &fakeContainerManager{ + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_PRIMARY.String()}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + }, + { + name: "location-label-among-other-labels", + inReq: &cpb.StartContainerRequest{ + ImageName: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + Labels: map[string]string{ + "x": "y", + locationLabel: cpb.StartContainerRequest_L_ALL.String(), + }, + Location: cpb.StartContainerRequest_L_ALL, + }, + wantResp: &cpb.StartContainerResponse{ + Response: &cpb.StartContainerResponse_StartOk{ + StartOk: &cpb.StartOK{}, + }, + }, + wantState: &fakeContainerManager{ + Labels: map[string]string{ + "x": "y", + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, + Image: "some-image", + Tag: "some-tag", + Cmd: "some-cmd", + }, + }, } for _, tc := range tests { @@ -380,12 +584,13 @@ func TestContainerStart(t *testing.T) { defer s.Halt(ctx) resp, err := cli.StartContainer(ctx, tc.inReq) - if err != nil { - t.Errorf("Start(%+v) returned error: %v", tc.inReq, err) + if !errors.Is(err, tc.wantErr) { + t.Errorf("expected Start(%+v) to return error %v, got error: %v", + tc.inReq, tc.wantErr, err) } if diff := cmp.Diff(tc.wantResp, resp, protocmp.Transform()); diff != "" { - t.Errorf("Start(%+v) returned diff (-want +got):\n%s", tc.inReq, diff) + t.Errorf("Start(%+v) returned resp diff (-want +got):\n%s", tc.inReq, diff) } if diff := cmp.Diff(tc.wantState, fake, protocmp.Transform(), cmpopts.IgnoreUnexported(fakeContainerManager{}), cmpopts.SortMaps(func(a, b string) bool { return a < b })); diff != "" { diff --git a/server/update_container.go b/server/update_container.go index c5618c9..33b9e2a 100644 --- a/server/update_container.go +++ b/server/update_container.go @@ -51,7 +51,10 @@ func (s *Server) UpdateContainer(ctx context.Context, request *cpb.UpdateContain return nil, status.Errorf(codes.FailedPrecondition, "expected request to contain populated params, yet was nil") } - opts := optionsFromStartContainerRequest(startReq) + opts, err := optionsFromStartContainerRequest(startReq) + if err != nil { + return nil, err + } instance, err := s.mgr.ContainerUpdate(ctx, request.GetInstanceName(), startReq.GetImageName(), startReq.GetTag(), startReq.GetCmd(), request.GetAsync(), opts...) if err != nil { return nil, err diff --git a/server/update_container_test.go b/server/update_container_test.go index 901f7a0..f004ad8 100644 --- a/server/update_container_test.go +++ b/server/update_container_test.go @@ -46,6 +46,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, }, }, wantResp: &cpb.UpdateContainerResponse{ @@ -62,6 +63,8 @@ func TestContainerUpdate(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Async: false, + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -75,6 +78,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, }, }, wantResp: &cpb.UpdateContainerResponse{ @@ -91,6 +95,8 @@ func TestContainerUpdate(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Async: false, + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -104,6 +110,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, }, }, wantResp: &cpb.UpdateContainerResponse{ @@ -120,6 +127,8 @@ func TestContainerUpdate(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Async: true, + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -133,6 +142,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Ports: []*cpb.StartContainerRequest_Port{ { Internal: 1, @@ -159,6 +169,8 @@ func TestContainerUpdate(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Ports: map[uint32]uint32{1: 2, 3: 4}, + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -172,6 +184,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Environment: map[string]string{"1": "2", "3": "4"}, }, }, @@ -189,6 +202,8 @@ func TestContainerUpdate(t *testing.T) { Tag: "some-tag", Cmd: "some-cmd", Envs: map[string]string{"1": "2", "3": "4"}, + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, }, }, { @@ -202,6 +217,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Volumes: []*cpb.Volume{ { Name: "vol1", @@ -228,6 +244,8 @@ func TestContainerUpdate(t *testing.T) { Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Volumes: []*cpb.Volume{ { Name: "vol1", @@ -252,6 +270,7 @@ func TestContainerUpdate(t *testing.T) { ImageName: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Location: cpb.StartContainerRequest_L_ALL, Devices: []*cpb.Device{ { SrcPath: "dev1", @@ -279,6 +298,8 @@ func TestContainerUpdate(t *testing.T) { Image: "some-image", Tag: "some-tag", Cmd: "some-cmd", + Labels: map[string]string{ + locationLabel: cpb.StartContainerRequest_L_ALL.String()}, Devices: []*cpb.Device{ { SrcPath: "dev1",