diff --git a/cmd/yurt-iot-dock/app/core.go b/cmd/yurt-iot-dock/app/core.go index 061dedbfe9e..032fbe49b5c 100644 --- a/cmd/yurt-iot-dock/app/core.go +++ b/cmd/yurt-iot-dock/app/core.go @@ -118,6 +118,10 @@ func Run(opts *options.YurtIoTDockOptions, stopCh <-chan struct{}) { } edgexdock := edgexclients.NewEdgexDock(opts.Version, opts.CoreMetadataAddr, opts.CoreCommandAddr) + if edgexdock == nil { + setupLog.Error(err, "failed to launch yurt-iot-dock, this maybe because the yurt-iot-dock is not compatible with the current edgex version") + os.Exit(1) + } // setup the DeviceProfile Reconciler and Syncer if err = (&controllers.DeviceProfileReconciler{ diff --git a/go.mod b/go.mod index 6bdad04c789..02ab1ea4ff4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.62.156 github.com/davecgh/go-spew v1.1.1 github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0 - github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0 github.com/go-resty/resty/v2 v2.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index e3fa03ef033..0a4ba2980f5 100644 --- a/go.sum +++ b/go.sum @@ -177,8 +177,6 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0 h1:8Svk1HTehXEgwxgyA4muVhSkP3D9n1q+oSHI3B1Ac90= github.com/edgexfoundry/go-mod-core-contracts/v2 v2.3.0/go.mod h1:4/e61acxVkhQWCTjQ4XcHVJDnrMDloFsZZB1B6STCRw= -github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0 h1:xjwCI34DLM31cSl1q9XmYgXS3JqXufQJMgohnLLLDx0= -github.com/edgexfoundry/go-mod-core-contracts/v3 v3.0.0/go.mod h1:zzzWGWij6wAqm1go9TLs++TFMIsBqBb1eRnIj4mRxGw= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/device_client.go b/pkg/yurtiotdock/clients/edgex-foundry/client/device_client.go similarity index 93% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/device_client.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/device_client.go index 771d1659f43..bd4fcc663c9 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/device_client.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/device_client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -39,33 +39,39 @@ import ( type EdgexDeviceClient struct { *resty.Client - CoreMetaAddr string - CoreCommandAddr string + CoreMetaAddr string + CoreCommandAddr string + APIVersion string + DevicePath string + CommandResponsePath string } -func NewEdgexDeviceClient(coreMetaAddr, coreCommandAddr string) *EdgexDeviceClient { +func NewEdgexDeviceClient(coreMetaAddr, coreCommandAddr, APIVersion string) *EdgexDeviceClient { cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) instance := resty.NewWithClient(&http.Client{ Jar: cookieJar, Timeout: 10 * time.Second, }) return &EdgexDeviceClient{ - Client: instance, - CoreMetaAddr: coreMetaAddr, - CoreCommandAddr: coreCommandAddr, + Client: instance, + CoreMetaAddr: coreMetaAddr, + CoreCommandAddr: coreCommandAddr, + APIVersion: APIVersion, + DevicePath: fmt.Sprintf(DevicePathTemplate, APIVersion), + CommandResponsePath: fmt.Sprintf(CommandResponsePathTemplate, APIVersion), } } // Create function sends a POST request to EdgeX to add a new device func (efc *EdgexDeviceClient) Create(ctx context.Context, device *iotv1alpha1.Device, options clients.CreateOptions) (*iotv1alpha1.Device, error) { devs := []*iotv1alpha1.Device{device} - req := makeEdgeXDeviceRequest(devs) + req := makeEdgeXDeviceRequest(devs, efc.APIVersion) klog.V(5).Infof("will add the Device: %s", device.Name) reqBody, err := json.Marshal(req) if err != nil { return nil, err } - postPath := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, DevicePath) + postPath := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, efc.DevicePath) resp, err := efc.R(). SetBody(reqBody).Post(postPath) if err != nil { @@ -95,7 +101,7 @@ func (efc *EdgexDeviceClient) Create(ctx context.Context, device *iotv1alpha1.De // Delete function sends a request to EdgeX to delete a device func (efc *EdgexDeviceClient) Delete(ctx context.Context, name string, options clients.DeleteOptions) error { klog.V(5).Infof("will delete the Device: %s", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, DevicePath, name) + delURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, efc.DevicePath, name) resp, err := efc.R().Delete(delURL) if err != nil { return err @@ -110,12 +116,12 @@ func (efc *EdgexDeviceClient) Delete(ctx context.Context, name string, options c // TODO support to update other fields func (efc *EdgexDeviceClient) Update(ctx context.Context, device *iotv1alpha1.Device, options clients.UpdateOptions) (*iotv1alpha1.Device, error) { actualDeviceName := getEdgeXName(device) - patchURL := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, DevicePath) + patchURL := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, efc.DevicePath) if device == nil { return nil, nil } devs := []*iotv1alpha1.Device{device} - req := makeEdgeXDeviceUpdateRequest(devs) + req := makeEdgeXDeviceUpdateRequest(devs, efc.APIVersion) reqBody, err := json.Marshal(req) if err != nil { return nil, err @@ -136,7 +142,7 @@ func (efc *EdgexDeviceClient) Update(ctx context.Context, device *iotv1alpha1.De func (efc *EdgexDeviceClient) Get(ctx context.Context, deviceName string, options clients.GetOptions) (*iotv1alpha1.Device, error) { klog.V(5).Infof("will get Devices: %s", deviceName) var dResp edgex_resp.DeviceResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, DevicePath, deviceName) + getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, efc.DevicePath, deviceName) resp, err := efc.R().Get(getURL) if err != nil { return nil, err @@ -155,7 +161,7 @@ func (efc *EdgexDeviceClient) Get(ctx context.Context, deviceName string, option // List is used to get all device objects on edge platform // TODO:support label filtering according to options func (efc *EdgexDeviceClient) List(ctx context.Context, options clients.ListOptions) ([]iotv1alpha1.Device, error) { - lp := fmt.Sprintf("http://%s%s/all?limit=-1", efc.CoreMetaAddr, DevicePath) + lp := fmt.Sprintf("http://%s%s/all?limit=-1", efc.CoreMetaAddr, efc.DevicePath) resp, err := efc.R().EnableTrace().Get(lp) if err != nil { return nil, err @@ -354,7 +360,7 @@ func (efc *EdgexDeviceClient) GetCommandResponseByName(deviceName string) ([]dto klog.V(5).Infof("will get CommandResponses of device: %s", deviceName) var dcr edgex_resp.DeviceCoreCommandResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreCommandAddr, CommandResponsePath, deviceName) + getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreCommandAddr, efc.CommandResponsePath, deviceName) resp, err := efc.R().Get(getURL) if err != nil { diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/device_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/client/device_client_test.go similarity index 99% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/device_client_test.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/device_client_test.go index f34d5b76c6a..d418782e37c 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/device_client_test.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/device_client_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -46,7 +46,7 @@ const ( DeviceUpdateProperty = `{"apiVersion":"v2","statusCode":200}` ) -var deviceClient = NewEdgexDeviceClient("edgex-core-metadata:59881", "edgex-core-command:59882") +var deviceClient = NewEdgexDeviceClient("edgex-core-metadata:59881", "edgex-core-command:59882", "v2") func Test_Get(t *testing.T) { httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client.go b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client.go similarity index 82% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client.go index 8f300278cf5..5abd2df5cb8 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -33,25 +33,31 @@ import ( type EdgexDeviceProfile struct { *resty.Client - CoreMetaAddr string + CoreMetaAddr string + APIVersion string + DeviceProfilePath string + CommandResponsePath string } -func NewEdgexDeviceProfile(coreMetaAddr string) *EdgexDeviceProfile { +func NewEdgexDeviceProfile(coreMetaAddr, APIVersion string) *EdgexDeviceProfile { return &EdgexDeviceProfile{ - Client: resty.New(), - CoreMetaAddr: coreMetaAddr, + Client: resty.New(), + CoreMetaAddr: coreMetaAddr, + APIVersion: APIVersion, + DeviceProfilePath: fmt.Sprintf(DeviceProfilePathTemplate, APIVersion), + CommandResponsePath: fmt.Sprintf(CommandResponsePathTemplate, APIVersion), } } // TODO: support label filtering -func getListDeviceProfileURL(address string, opts devcli.ListOptions) (string, error) { - url := fmt.Sprintf("http://%s%s/all?limit=-1", address, DeviceProfilePath) +func (cdc *EdgexDeviceProfile) getListDeviceProfileURL(address string, opts devcli.ListOptions) (string, error) { + url := fmt.Sprintf("http://%s%s/all?limit=-1", address, cdc.DeviceProfilePath) return url, nil } func (cdc *EdgexDeviceProfile) List(ctx context.Context, opts devcli.ListOptions) ([]v1alpha1.DeviceProfile, error) { klog.V(5).Info("will list DeviceProfiles") - lp, err := getListDeviceProfileURL(cdc.CoreMetaAddr, opts) + lp, err := cdc.getListDeviceProfileURL(cdc.CoreMetaAddr, opts) if err != nil { return nil, err } @@ -73,7 +79,7 @@ func (cdc *EdgexDeviceProfile) List(ctx context.Context, opts devcli.ListOptions func (cdc *EdgexDeviceProfile) Get(ctx context.Context, name string, opts devcli.GetOptions) (*v1alpha1.DeviceProfile, error) { klog.V(5).Infof("will get DeviceProfiles: %s", name) var dpResp responses.DeviceProfileResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, DeviceProfilePath, name) + getURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, cdc.DeviceProfilePath, name) resp, err := cdc.R().Get(getURL) if err != nil { return nil, err @@ -90,13 +96,13 @@ func (cdc *EdgexDeviceProfile) Get(ctx context.Context, name string, opts devcli func (cdc *EdgexDeviceProfile) Create(ctx context.Context, deviceProfile *v1alpha1.DeviceProfile, opts devcli.CreateOptions) (*v1alpha1.DeviceProfile, error) { dps := []*v1alpha1.DeviceProfile{deviceProfile} - req := makeEdgeXDeviceProfilesRequest(dps) + req := makeEdgeXDeviceProfilesRequest(dps, cdc.APIVersion) klog.V(5).Infof("will add the DeviceProfile: %s", deviceProfile.Name) reqBody, err := json.Marshal(req) if err != nil { return nil, err } - postURL := fmt.Sprintf("http://%s%s", cdc.CoreMetaAddr, DeviceProfilePath) + postURL := fmt.Sprintf("http://%s%s", cdc.CoreMetaAddr, cdc.DeviceProfilePath) resp, err := cdc.R().SetBody(reqBody).Post(postURL) if err != nil { return nil, err @@ -129,7 +135,7 @@ func (cdc *EdgexDeviceProfile) Update(ctx context.Context, deviceProfile *v1alph func (cdc *EdgexDeviceProfile) Delete(ctx context.Context, name string, opts devcli.DeleteOptions) error { klog.V(5).Infof("will delete the DeviceProfile: %s", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, DeviceProfilePath, name) + delURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, cdc.DeviceProfilePath, name) resp, err := cdc.R().Delete(delURL) if err != nil { return err diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client_test.go similarity index 99% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client_test.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client_test.go index ba6e2599021..cbf5e0190b5 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceprofile_client_test.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceprofile_client_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -38,7 +38,7 @@ const ( ProfileDeleteFail = `{"apiVersion":"v2","message":"fail to delete the device profile with name test-Random-Boolean-Device","statusCode":404}` ) -var profileClient = NewEdgexDeviceProfile("edgex-core-metadata:59881") +var profileClient = NewEdgexDeviceProfile("edgex-core-metadata:59881", "v2") func Test_ListProfile(t *testing.T) { diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client.go b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client.go similarity index 87% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client.go index 48d74e223ce..5b56a84d3a0 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -33,26 +33,32 @@ import ( type EdgexDeviceServiceClient struct { *resty.Client - CoreMetaAddr string + CoreMetaAddr string + APIVersion string + DeviceServicePath string + CommandResponsePath string } -func NewEdgexDeviceServiceClient(coreMetaAddr string) *EdgexDeviceServiceClient { +func NewEdgexDeviceServiceClient(coreMetaAddr, APIVersion string) *EdgexDeviceServiceClient { return &EdgexDeviceServiceClient{ - Client: resty.New(), - CoreMetaAddr: coreMetaAddr, + Client: resty.New(), + CoreMetaAddr: coreMetaAddr, + APIVersion: APIVersion, + DeviceServicePath: fmt.Sprintf(DeviceServicePathTemplate, APIVersion), + CommandResponsePath: fmt.Sprintf(CommandResponsePathTemplate, APIVersion), } } // Create function sends a POST request to EdgeX to add a new deviceService func (eds *EdgexDeviceServiceClient) Create(ctx context.Context, deviceService *v1alpha1.DeviceService, options edgeCli.CreateOptions) (*v1alpha1.DeviceService, error) { dss := []*v1alpha1.DeviceService{deviceService} - req := makeEdgeXDeviceService(dss) + req := makeEdgeXDeviceService(dss, eds.APIVersion) klog.V(5).InfoS("will add the DeviceServices", "DeviceService", deviceService.Name) jsonBody, err := json.Marshal(req) if err != nil { return nil, err } - postPath := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, DeviceServicePath) + postPath := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, eds.DeviceServicePath) resp, err := eds.R(). SetBody(jsonBody).Post(postPath) if err != nil { @@ -82,7 +88,7 @@ func (eds *EdgexDeviceServiceClient) Create(ctx context.Context, deviceService * // Delete function sends a request to EdgeX to delete a deviceService func (eds *EdgexDeviceServiceClient) Delete(ctx context.Context, name string, option edgeCli.DeleteOptions) error { klog.V(5).InfoS("will delete the DeviceService", "DeviceService", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, DeviceServicePath, name) + delURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, eds.DeviceServicePath, name) resp, err := eds.R().Delete(delURL) if err != nil { return err @@ -96,7 +102,7 @@ func (eds *EdgexDeviceServiceClient) Delete(ctx context.Context, name string, op // Update is used to set the admin or operating state of the deviceService by unique name of the deviceService. // TODO support to update other fields func (eds *EdgexDeviceServiceClient) Update(ctx context.Context, ds *v1alpha1.DeviceService, options edgeCli.UpdateOptions) (*v1alpha1.DeviceService, error) { - patchURL := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, DeviceServicePath) + patchURL := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, eds.DeviceServicePath) if ds == nil { return nil, nil } @@ -127,7 +133,7 @@ func (eds *EdgexDeviceServiceClient) Update(ctx context.Context, ds *v1alpha1.De func (eds *EdgexDeviceServiceClient) Get(ctx context.Context, name string, options edgeCli.GetOptions) (*v1alpha1.DeviceService, error) { klog.V(5).InfoS("will get DeviceServices", "DeviceService", name) var dsResp responses.DeviceServiceResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, DeviceServicePath, name) + getURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, eds.DeviceServicePath, name) resp, err := eds.R().Get(getURL) if err != nil { return nil, err @@ -147,7 +153,7 @@ func (eds *EdgexDeviceServiceClient) Get(ctx context.Context, name string, optio // The Hanoi version currently supports only a single label and does not support other filters func (eds *EdgexDeviceServiceClient) List(ctx context.Context, options edgeCli.ListOptions) ([]v1alpha1.DeviceService, error) { klog.V(5).Info("will list DeviceServices") - lp := fmt.Sprintf("http://%s%s/all?limit=-1", eds.CoreMetaAddr, DeviceServicePath) + lp := fmt.Sprintf("http://%s%s/all?limit=-1", eds.CoreMetaAddr, eds.DeviceServicePath) resp, err := eds.R(). EnableTrace(). Get(lp) diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client_test.go similarity index 99% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client_test.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client_test.go index def719cf836..fea64cd032a 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/deviceservice_client_test.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/deviceservice_client_test.go @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "context" @@ -40,7 +40,7 @@ const ( ServiceUpdateFail = `[{"apiVersion":"v2","message":"fail to query object *models.DeviceService, because id: md|ds:01dfe04d-f361-41fd-b1c4-7ca0718f461a doesn't exist in the database","statusCode":404}]` ) -var serviceClient = NewEdgexDeviceServiceClient("edgex-core-metadata:59881") +var serviceClient = NewEdgexDeviceServiceClient("edgex-core-metadata:59881", "v2") func Test_GetService(t *testing.T) { httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v2/util.go b/pkg/yurtiotdock/clients/edgex-foundry/client/util.go similarity index 94% rename from pkg/yurtiotdock/clients/edgex-foundry/v2/util.go rename to pkg/yurtiotdock/clients/edgex-foundry/client/util.go index 40753cda5bd..e9668bdf18e 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/v2/util.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/client/util.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package client import ( "fmt" @@ -30,13 +30,11 @@ import ( ) const ( - EdgeXObjectName = "yurt-iot-dock/edgex-object.name" - DeviceServicePath = "/api/v2/deviceservice" - DeviceProfilePath = "/api/v2/deviceprofile" - DevicePath = "/api/v2/device" - CommandResponsePath = "/api/v2/device" - - APIVersionV2 = "v2" + EdgeXObjectName = "yurt-iot-dock/edgex-object.name" + DeviceServicePathTemplate = "/api/%s/deviceservice" + DeviceProfilePathTemplate = "/api/%s/deviceprofile" + DevicePathTemplate = "/api/%s/device" + CommandResponsePathTemplate = "/api/%s/device" ) type ClientURL struct { @@ -391,13 +389,13 @@ func toEdgeXDeviceProfile(dp *iotv1alpha1.DeviceProfile) dtos.DeviceProfile { } } -func makeEdgeXDeviceProfilesRequest(dps []*iotv1alpha1.DeviceProfile) []*requests.DeviceProfileRequest { +func makeEdgeXDeviceProfilesRequest(dps []*iotv1alpha1.DeviceProfile, APIVersion string) []*requests.DeviceProfileRequest { var req []*requests.DeviceProfileRequest for _, dp := range dps { req = append(req, &requests.DeviceProfileRequest{ BaseRequest: common.BaseRequest{ Versionable: common.Versionable{ - ApiVersion: APIVersionV2, + ApiVersion: APIVersion, }, }, Profile: toEdgeXDeviceProfile(dp), @@ -406,13 +404,13 @@ func makeEdgeXDeviceProfilesRequest(dps []*iotv1alpha1.DeviceProfile) []*request return req } -func makeEdgeXDeviceUpdateRequest(devs []*iotv1alpha1.Device) []*requests.UpdateDeviceRequest { +func makeEdgeXDeviceUpdateRequest(devs []*iotv1alpha1.Device, APIVersion string) []*requests.UpdateDeviceRequest { var req []*requests.UpdateDeviceRequest for _, dev := range devs { req = append(req, &requests.UpdateDeviceRequest{ BaseRequest: common.BaseRequest{ Versionable: common.Versionable{ - ApiVersion: APIVersionV2, + ApiVersion: APIVersion, }, }, Device: toEdgeXUpdateDevice(dev), @@ -421,13 +419,13 @@ func makeEdgeXDeviceUpdateRequest(devs []*iotv1alpha1.Device) []*requests.Update return req } -func makeEdgeXDeviceRequest(devs []*iotv1alpha1.Device) []*requests.AddDeviceRequest { +func makeEdgeXDeviceRequest(devs []*iotv1alpha1.Device, APIVersion string) []*requests.AddDeviceRequest { var req []*requests.AddDeviceRequest for _, dev := range devs { req = append(req, &requests.AddDeviceRequest{ BaseRequest: common.BaseRequest{ Versionable: common.Versionable{ - ApiVersion: APIVersionV2, + ApiVersion: APIVersion, }, }, Device: toEdgeXDevice(dev), @@ -436,13 +434,13 @@ func makeEdgeXDeviceRequest(devs []*iotv1alpha1.Device) []*requests.AddDeviceReq return req } -func makeEdgeXDeviceService(dss []*iotv1alpha1.DeviceService) []*requests.AddDeviceServiceRequest { +func makeEdgeXDeviceService(dss []*iotv1alpha1.DeviceService, APIVersion string) []*requests.AddDeviceServiceRequest { var req []*requests.AddDeviceServiceRequest for _, ds := range dss { req = append(req, &requests.AddDeviceServiceRequest{ BaseRequest: common.BaseRequest{ Versionable: common.Versionable{ - ApiVersion: APIVersionV2, + ApiVersion: APIVersion, }, }, Service: toEdgexDeviceService(ds), diff --git a/pkg/yurtiotdock/clients/edgex-foundry/edgexobject.go b/pkg/yurtiotdock/clients/edgex-foundry/edgexobject.go index 93159fb31aa..c07f2d2341e 100644 --- a/pkg/yurtiotdock/clients/edgex-foundry/edgexobject.go +++ b/pkg/yurtiotdock/clients/edgex-foundry/edgexobject.go @@ -17,11 +17,9 @@ limitations under the License. package edgex_foundry import ( - "fmt" - "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" - edgexcliv2 "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients/edgex-foundry/v2" - edgexcliv3 "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients/edgex-foundry/v3" + edgexcli "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients/edgex-foundry/client" + "k8s.io/klog/v2" ) type EdgeXObject interface { @@ -30,47 +28,41 @@ type EdgeXObject interface { type EdgexDock struct { Version string + APIVersion string CoreMetadataAddr string CoreCommandAddr string } func NewEdgexDock(version string, coreMetadataAddr string, coreCommandAddr string) *EdgexDock { - return &EdgexDock{ + edgexdock := &EdgexDock{ Version: version, CoreMetadataAddr: coreMetadataAddr, CoreCommandAddr: coreCommandAddr, } -} -func (ep *EdgexDock) CreateDeviceClient() (clients.DeviceInterface, error) { - switch ep.Version { + // TODO: In version 1.5 we will distinguish between v2 and v3 implementations, and v3 will use messagebus. + // Refer this proposal: https://github.com/openyurtio/openyurt/pull/1680 + switch version { case "minnesota": - return edgexcliv3.NewEdgexDeviceClient(ep.CoreMetadataAddr, ep.CoreCommandAddr), nil + edgexdock.APIVersion = "v3" case "levski", "kamakura", "jakarta": - return edgexcliv2.NewEdgexDeviceClient(ep.CoreMetadataAddr, ep.CoreCommandAddr), nil + edgexdock.APIVersion = "v2" default: - return nil, fmt.Errorf("unsupported Edgex version: %v", ep.Version) + klog.Errorf("unsupported Edgex version: %v", version) + return nil } + + return edgexdock +} + +func (ep *EdgexDock) CreateDeviceClient() (clients.DeviceInterface, error) { + return edgexcli.NewEdgexDeviceClient(ep.CoreMetadataAddr, ep.CoreCommandAddr, ep.APIVersion), nil } func (ep *EdgexDock) CreateDeviceProfileClient() (clients.DeviceProfileInterface, error) { - switch ep.Version { - case "minnesota": - return edgexcliv3.NewEdgexDeviceProfile(ep.CoreMetadataAddr), nil - case "levski", "kamakura", "jakarta": - return edgexcliv2.NewEdgexDeviceProfile(ep.CoreMetadataAddr), nil - default: - return nil, fmt.Errorf("unsupported Edgex version: %v", ep.Version) - } + return edgexcli.NewEdgexDeviceProfile(ep.CoreMetadataAddr, ep.APIVersion), nil } func (ep *EdgexDock) CreateDeviceServiceClient() (clients.DeviceServiceInterface, error) { - switch ep.Version { - case "minnesota": - return edgexcliv3.NewEdgexDeviceServiceClient(ep.CoreMetadataAddr), nil - case "levski", "kamakura", "jakarta": - return edgexcliv2.NewEdgexDeviceServiceClient(ep.CoreMetadataAddr), nil - default: - return nil, fmt.Errorf("unsupported Edgex version: %v", ep.Version) - } + return edgexcli.NewEdgexDeviceServiceClient(ep.CoreMetadataAddr, ep.APIVersion), nil } diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client.go deleted file mode 100644 index 609c99fe8bf..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client.go +++ /dev/null @@ -1,371 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v3 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/http/cookiejar" - "strings" - "time" - - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common" - edgex_resp "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/go-resty/resty/v2" - "golang.org/x/net/publicsuffix" - "k8s.io/klog/v2" - - iotv1alpha1 "github.com/openyurtio/openyurt/pkg/apis/iot/v1alpha1" - "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -type EdgexDeviceClient struct { - *resty.Client - CoreMetaAddr string - CoreCommandAddr string -} - -func NewEdgexDeviceClient(coreMetaAddr, coreCommandAddr string) *EdgexDeviceClient { - cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) - instance := resty.NewWithClient(&http.Client{ - Jar: cookieJar, - Timeout: 10 * time.Second, - }) - return &EdgexDeviceClient{ - Client: instance, - CoreMetaAddr: coreMetaAddr, - CoreCommandAddr: coreCommandAddr, - } -} - -// Create function sends a POST request to EdgeX to add a new device -func (efc *EdgexDeviceClient) Create(ctx context.Context, device *iotv1alpha1.Device, options clients.CreateOptions) (*iotv1alpha1.Device, error) { - devs := []*iotv1alpha1.Device{device} - req := makeEdgeXDeviceRequest(devs) - klog.V(5).Infof("will add the Device: %s", device.Name) - reqBody, err := json.Marshal(req) - if err != nil { - return nil, err - } - postPath := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, DevicePath) - resp, err := efc.R(). - SetBody(reqBody).Post(postPath) - if err != nil { - return nil, err - } else if resp.StatusCode() != http.StatusMultiStatus { - return nil, fmt.Errorf("create device on edgex foundry failed, the response is : %s", resp.Body()) - } - - var edgexResps []*common.BaseWithIdResponse - if err = json.Unmarshal(resp.Body(), &edgexResps); err != nil { - return nil, err - } - createdDevice := device.DeepCopy() - if len(edgexResps) == 1 { - if edgexResps[0].StatusCode == http.StatusCreated { - createdDevice.Status.EdgeId = edgexResps[0].Id - createdDevice.Status.Synced = true - } else { - return nil, fmt.Errorf("create device on edgex foundry failed, the response is : %s", resp.Body()) - } - } else { - return nil, fmt.Errorf("edgex BaseWithIdResponse count mismatch device cound, the response is : %s", resp.Body()) - } - return createdDevice, err -} - -// Delete function sends a request to EdgeX to delete a device -func (efc *EdgexDeviceClient) Delete(ctx context.Context, name string, options clients.DeleteOptions) error { - klog.V(5).Infof("will delete the Device: %s", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, DevicePath, name) - resp, err := efc.R().Delete(delURL) - if err != nil { - return err - } - if resp.StatusCode() != http.StatusOK { - return errors.New(string(resp.Body())) - } - return nil -} - -// Update is used to set the admin or operating state of the device by unique name of the device. -// TODO support to update other fields -func (efc *EdgexDeviceClient) Update(ctx context.Context, device *iotv1alpha1.Device, options clients.UpdateOptions) (*iotv1alpha1.Device, error) { - actualDeviceName := getEdgeXName(device) - patchURL := fmt.Sprintf("http://%s%s", efc.CoreMetaAddr, DevicePath) - if device == nil { - return nil, nil - } - devs := []*iotv1alpha1.Device{device} - req := makeEdgeXDeviceUpdateRequest(devs) - reqBody, err := json.Marshal(req) - if err != nil { - return nil, err - } - rep, err := efc.R(). - SetHeader("Content-Type", "application/json"). - SetBody(reqBody). - Patch(patchURL) - if err != nil { - return nil, err - } else if rep.StatusCode() != http.StatusMultiStatus { - return nil, fmt.Errorf("failed to update device: %s, get response: %s", actualDeviceName, string(rep.Body())) - } - return device, nil -} - -// Get is used to query the device information corresponding to the device name -func (efc *EdgexDeviceClient) Get(ctx context.Context, deviceName string, options clients.GetOptions) (*iotv1alpha1.Device, error) { - klog.V(5).Infof("will get Devices: %s", deviceName) - var dResp edgex_resp.DeviceResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreMetaAddr, DevicePath, deviceName) - resp, err := efc.R().Get(getURL) - if err != nil { - return nil, err - } - if resp.StatusCode() == http.StatusNotFound { - return nil, fmt.Errorf("Device %s not found", deviceName) - } - err = json.Unmarshal(resp.Body(), &dResp) - if err != nil { - return nil, err - } - device := toKubeDevice(dResp.Device, options.Namespace) - return &device, err -} - -// List is used to get all device objects on edge platform -// TODO:support label filtering according to options -func (efc *EdgexDeviceClient) List(ctx context.Context, options clients.ListOptions) ([]iotv1alpha1.Device, error) { - lp := fmt.Sprintf("http://%s%s/all?limit=-1", efc.CoreMetaAddr, DevicePath) - resp, err := efc.R().EnableTrace().Get(lp) - if err != nil { - return nil, err - } - var mdResp edgex_resp.MultiDevicesResponse - if err := json.Unmarshal(resp.Body(), &mdResp); err != nil { - return nil, err - } - var res []iotv1alpha1.Device - for _, dp := range mdResp.Devices { - res = append(res, toKubeDevice(dp, options.Namespace)) - } - return res, nil -} - -func (efc *EdgexDeviceClient) GetPropertyState(ctx context.Context, propertyName string, d *iotv1alpha1.Device, options clients.GetOptions) (*iotv1alpha1.ActualPropertyState, error) { - actualDeviceName := getEdgeXName(d) - // get the old property from status - oldAps, exist := d.Status.DeviceProperties[propertyName] - propertyGetURL := "" - // 1. query the Get URL of a property - if !exist || (exist && oldAps.GetURL == "") { - coreCommands, err := efc.GetCommandResponseByName(actualDeviceName) - if err != nil { - return &iotv1alpha1.ActualPropertyState{}, err - } - for _, c := range coreCommands { - if c.Name == propertyName && c.Get { - propertyGetURL = fmt.Sprintf("%s%s", c.Url, c.Path) - break - } - } - if propertyGetURL == "" { - return nil, &clients.NotFoundError{} - } - } else { - propertyGetURL = oldAps.GetURL - } - // 2. get the actual property value by the getURL - actualPropertyState := iotv1alpha1.ActualPropertyState{ - Name: propertyName, - GetURL: propertyGetURL, - } - if resp, err := efc.getPropertyState(propertyGetURL); err != nil { - return nil, err - } else { - var eResp edgex_resp.EventResponse - if err := json.Unmarshal(resp.Body(), &eResp); err != nil { - return nil, err - } - actualPropertyState.ActualValue = getPropertyValueFromEvent(propertyName, eResp.Event) - } - return &actualPropertyState, nil -} - -// getPropertyState returns different error messages according to the status code -func (efc *EdgexDeviceClient) getPropertyState(getURL string) (*resty.Response, error) { - resp, err := efc.R().Get(getURL) - if err != nil { - return resp, err - } - if resp.StatusCode() == 400 { - err = errors.New("request is in an invalid state") - } else if resp.StatusCode() == 404 { - err = errors.New("the requested resource does not exist") - } else if resp.StatusCode() == 423 { - err = errors.New("the device is locked (AdminState) or down (OperatingState)") - } else if resp.StatusCode() == 500 { - err = errors.New("an unexpected error occurred on the server") - } - return resp, err -} - -func (efc *EdgexDeviceClient) UpdatePropertyState(ctx context.Context, propertyName string, d *iotv1alpha1.Device, options clients.UpdateOptions) error { - // Get the actual device name - acturalDeviceName := getEdgeXName(d) - - dps := d.Spec.DeviceProperties[propertyName] - parameterName := dps.Name - if dps.PutURL == "" { - putCmd, err := efc.getPropertyPut(acturalDeviceName, dps.Name) - if err != nil { - return err - } - dps.PutURL = fmt.Sprintf("%s%s", putCmd.Url, putCmd.Path) - if len(putCmd.Parameters) == 1 { - parameterName = putCmd.Parameters[0].ResourceName - } - } - // set the device property to desired state - bodyMap := make(map[string]string) - bodyMap[parameterName] = dps.DesiredValue - body, _ := json.Marshal(bodyMap) - klog.V(5).Infof("setting the property to desired value", "propertyName", parameterName, "desiredValue", string(body)) - rep, err := efc.R(). - SetHeader("Content-Type", "application/json"). - SetBody(body). - Put(dps.PutURL) - if err != nil { - return err - } else if rep.StatusCode() != http.StatusOK { - return fmt.Errorf("failed to set property: %s, get response: %s", dps.Name, string(rep.Body())) - } else if rep.Body() != nil { - // If the parameters are illegal, such as out of range, the 200 status code is also returned, but the description appears in the body - a := string(rep.Body()) - if strings.Contains(a, "execWriteCmd") { - return fmt.Errorf("failed to set property: %s, get response: %s", dps.Name, string(rep.Body())) - } - } - return nil -} - -// Gets the models.Put from edgex foundry which is used to set the device property's value -func (efc *EdgexDeviceClient) getPropertyPut(deviceName, cmdName string) (dtos.CoreCommand, error) { - coreCommands, err := efc.GetCommandResponseByName(deviceName) - if err != nil { - return dtos.CoreCommand{}, err - } - for _, c := range coreCommands { - if cmdName == c.Name && c.Set { - return c, nil - } - } - return dtos.CoreCommand{}, errors.New("corresponding command is not found") -} - -// ListPropertiesState gets all the actual property information about a device -func (efc *EdgexDeviceClient) ListPropertiesState(ctx context.Context, device *iotv1alpha1.Device, options clients.ListOptions) (map[string]iotv1alpha1.DesiredPropertyState, map[string]iotv1alpha1.ActualPropertyState, error) { - actualDeviceName := getEdgeXName(device) - - dpsm := map[string]iotv1alpha1.DesiredPropertyState{} - apsm := map[string]iotv1alpha1.ActualPropertyState{} - coreCommands, err := efc.GetCommandResponseByName(actualDeviceName) - if err != nil { - return dpsm, apsm, err - } - - for _, c := range coreCommands { - // DesiredPropertyState only store the basic information and does not set DesiredValue - if c.Get { - getURL := fmt.Sprintf("%s%s", c.Url, c.Path) - aps, ok := apsm[c.Name] - if ok { - aps.GetURL = getURL - } else { - aps = iotv1alpha1.ActualPropertyState{Name: c.Name, GetURL: getURL} - } - apsm[c.Name] = aps - resp, err := efc.getPropertyState(getURL) - if err != nil { - klog.V(5).ErrorS(err, "getPropertyState failed", "propertyName", c.Name, "deviceName", actualDeviceName) - } else { - var eResp edgex_resp.EventResponse - if err := json.Unmarshal(resp.Body(), &eResp); err != nil { - klog.V(5).ErrorS(err, "failed to decode the response ", "response", resp) - continue - } - event := eResp.Event - readingName := c.Name - expectParams := c.Parameters - if len(expectParams) == 1 { - readingName = expectParams[0].ResourceName - } - klog.V(5).Infof("get reading name %s for command %s of device %s", readingName, c.Name, device.Name) - actualValue := getPropertyValueFromEvent(readingName, event) - aps.ActualValue = actualValue - apsm[c.Name] = aps - } - } - } - return dpsm, apsm, nil -} - -// The actual property value is resolved from the returned event -func getPropertyValueFromEvent(resName string, event dtos.Event) string { - actualValue := "" - for _, r := range event.Readings { - if resName == r.ResourceName { - if r.SimpleReading.Value != "" { - actualValue = r.SimpleReading.Value - } else if len(r.BinaryReading.BinaryValue) != 0 { - // TODO: how to demonstrate binary data - actualValue = fmt.Sprintf("%s:%s", r.BinaryReading.MediaType, "blob value") - } else if r.ObjectReading.ObjectValue != nil { - serializedBytes, _ := json.Marshal(r.ObjectReading.ObjectValue) - actualValue = string(serializedBytes) - } - break - } - } - return actualValue -} - -// GetCommandResponseByName gets all commands supported by the device -func (efc *EdgexDeviceClient) GetCommandResponseByName(deviceName string) ([]dtos.CoreCommand, error) { - klog.V(5).Infof("will get CommandResponses of device: %s", deviceName) - - var dcr edgex_resp.DeviceCoreCommandResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", efc.CoreCommandAddr, CommandResponsePath, deviceName) - - resp, err := efc.R().Get(getURL) - if err != nil { - return nil, err - } - if resp.StatusCode() == http.StatusNotFound { - return nil, errors.New("Item not found") - } - err = json.Unmarshal(resp.Body(), &dcr) - if err != nil { - return nil, err - } - return dcr.DeviceCoreCommand.CoreCommands, nil -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client_test.go deleted file mode 100644 index 4f6191e9c10..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/device_client_test.go +++ /dev/null @@ -1,202 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package v3 - -import ( - "context" - "encoding/json" - "testing" - - edgex_resp "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/jarcoal/httpmock" - "github.com/stretchr/testify/assert" - - iotv1alpha1 "github.com/openyurtio/openyurt/pkg/apis/iot/v1alpha1" - "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -const ( - DeviceListMetadata = `{"apiVersion":"v3","statusCode":200,"totalCount":5,"devices":[{"created":1661829206505,"modified":1661829206505,"id":"f6255845-f4b2-4182-bd3c-abc9eac4a649","name":"Random-Float-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-Float-Device","autoEvents":[{"interval":"30s","onChange":false,"sourceName":"Float32"},{"interval":"30s","onChange":false,"sourceName":"Float64"}],"protocols":{"other":{"Address":"device-virtual-float-01","Protocol":"300"}}},{"created":1661829206506,"modified":1661829206506,"id":"d29efe20-fdec-4aeb-90e5-99528cb6ca28","name":"Random-Binary-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-Binary-Device","protocols":{"other":{"Address":"device-virtual-binary-01","Port":"300"}}},{"created":1661829206504,"modified":1661829206504,"id":"6a7f00a4-9536-48b2-9380-a9fc202ac517","name":"Random-Integer-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-Integer-Device","autoEvents":[{"interval":"15s","onChange":false,"sourceName":"Int8"},{"interval":"15s","onChange":false,"sourceName":"Int16"},{"interval":"15s","onChange":false,"sourceName":"Int32"},{"interval":"15s","onChange":false,"sourceName":"Int64"}],"protocols":{"other":{"Address":"device-virtual-int-01","Protocol":"300"}}},{"created":1661829206503,"modified":1661829206503,"id":"439d47a2-fa72-4c27-9f47-c19356cc0c3b","name":"Random-Boolean-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-Boolean-Device","autoEvents":[{"interval":"10s","onChange":false,"sourceName":"Bool"}],"protocols":{"other":{"Address":"device-virtual-bool-01","Port":"300"}}},{"created":1661829206505,"modified":1661829206505,"id":"2890ab86-3ae4-4b5e-98ab-aad85fc540e6","name":"Random-UnsignedInteger-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-UnsignedInteger-Device","autoEvents":[{"interval":"20s","onChange":false,"sourceName":"Uint8"},{"interval":"20s","onChange":false,"sourceName":"Uint16"},{"interval":"20s","onChange":false,"sourceName":"Uint32"},{"interval":"20s","onChange":false,"sourceName":"Uint64"}],"protocols":{"other":{"Address":"device-virtual-uint-01","Protocol":"300"}}}]}` - DeviceMetadata = `{"apiVersion":"v3","statusCode":200,"device":{"created":1661829206505,"modified":1661829206505,"id":"f6255845-f4b2-4182-bd3c-abc9eac4a649","name":"Random-Float-Device","description":"Example of Device Virtual","adminState":"UNLOCKED","operatingState":"UP","labels":["device-virtual-example"],"serviceName":"device-virtual","profileName":"Random-Float-Device","autoEvents":[{"interval":"30s","onChange":false,"sourceName":"Float32"},{"interval":"30s","onChange":false,"sourceName":"Float64"}],"protocols":{"other":{"Address":"device-virtual-float-01","Protocol":"300"}}}}` - - DeviceCreateSuccess = `[{"apiVersion":"v3","statusCode":201,"id":"2fff4f1a-7110-442f-b347-9f896338ba57"}]` - DeviceCreateFail = `[{"apiVersion":"v3","message":"device name test-Random-Float-Device already exists","statusCode":409}]` - - DeviceDeleteSuccess = `{"apiVersion":"v3","statusCode":200}` - DeviceDeleteFail = `{"apiVersion":"v3","message":"fail to query device by name test-Random-Float-Device","statusCode":404}` - - DeviceCoreCommands = `{"apiVersion":"v3","statusCode":200,"deviceCoreCommand":{"deviceName":"Random-Float-Device","profileName":"Random-Float-Device","coreCommands":[{"name":"WriteFloat32ArrayValue","set":true,"path":"/api/v3/device/name/Random-Float-Device/WriteFloat32ArrayValue","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float32Array","valueType":"Float32Array"},{"resourceName":"EnableRandomization_Float32Array","valueType":"Bool"}]},{"name":"WriteFloat64ArrayValue","set":true,"path":"/api/v3/device/name/Random-Float-Device/WriteFloat64ArrayValue","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float64Array","valueType":"Float64Array"},{"resourceName":"EnableRandomization_Float64Array","valueType":"Bool"}]},{"name":"Float32","get":true,"set":true,"path":"/api/v3/device/name/Random-Float-Device/Float32","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float32","valueType":"Float32"}]},{"name":"Float64","get":true,"set":true,"path":"/api/v3/device/name/Random-Float-Device/Float64","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float64","valueType":"Float64"}]},{"name":"Float32Array","get":true,"set":true,"path":"/api/v3/device/name/Random-Float-Device/Float32Array","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float32Array","valueType":"Float32Array"}]},{"name":"Float64Array","get":true,"set":true,"path":"/api/v3/device/name/Random-Float-Device/Float64Array","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float64Array","valueType":"Float64Array"}]},{"name":"WriteFloat32Value","set":true,"path":"/api/v3/device/name/Random-Float-Device/WriteFloat32Value","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float32","valueType":"Float32"},{"resourceName":"EnableRandomization_Float32","valueType":"Bool"}]},{"name":"WriteFloat64Value","set":true,"path":"/api/v3/device/name/Random-Float-Device/WriteFloat64Value","url":"http://edgex-core-command:59882","parameters":[{"resourceName":"Float64","valueType":"Float64"},{"resourceName":"EnableRandomization_Float64","valueType":"Bool"}]}]}}` - DeviceCommandResp = `{"apiVersion":"v3","statusCode":200,"event":{"apiVersion":"v3","id":"095090e4-de39-45a1-a0fa-18bc340104e6","deviceName":"Random-Float-Device","profileName":"Random-Float-Device","sourceName":"Float32","origin":1661851070562067780,"readings":[{"id":"972bf6be-3b01-49fc-b211-a43ed51d207d","origin":1661851070562067780,"deviceName":"Random-Float-Device","resourceName":"Float32","profileName":"Random-Float-Device","valueType":"Float32","value":"-2.038811e+38"}]}}` - - DeviceUpdateSuccess = `[{"apiVersion":"v3","statusCode":200}] ` - - DeviceUpdateProperty = `{"apiVersion":"v3","statusCode":200}` -) - -var deviceClient = NewEdgexDeviceClient("edgex-core-metadata:59881", "edgex-core-command:59882") - -func Test_Get(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/device/name/Random-Float-Device", - httpmock.NewStringResponder(200, DeviceMetadata)) - - device, err := deviceClient.Get(context.TODO(), "Random-Float-Device", clients.GetOptions{Namespace: "default"}) - assert.Nil(t, err) - - assert.Equal(t, "Random-Float-Device", device.Spec.Profile) -} - -func Test_List(t *testing.T) { - - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/device/all?limit=-1", - httpmock.NewStringResponder(200, DeviceListMetadata)) - - devices, err := deviceClient.List(context.TODO(), clients.ListOptions{Namespace: "default"}) - assert.Nil(t, err) - - assert.Equal(t, len(devices), 5) -} - -func Test_Create(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/device", - httpmock.NewStringResponder(207, DeviceCreateSuccess)) - - var resp edgex_resp.DeviceResponse - - err := json.Unmarshal([]byte(DeviceMetadata), &resp) - assert.Nil(t, err) - - device := toKubeDevice(resp.Device, "default") - device.Name = "test-Random-Float-Device" - - create, err := deviceClient.Create(context.TODO(), &device, clients.CreateOptions{}) - assert.Nil(t, err) - - assert.Equal(t, "test-Random-Float-Device", create.Name) - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/device", - httpmock.NewStringResponder(207, DeviceCreateFail)) - - create, err = deviceClient.Create(context.TODO(), &device, clients.CreateOptions{}) - assert.NotNil(t, err) - assert.Nil(t, create) -} - -func Test_Delete(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/device/name/test-Random-Float-Device", - httpmock.NewStringResponder(200, DeviceDeleteSuccess)) - - err := deviceClient.Delete(context.TODO(), "test-Random-Float-Device", clients.DeleteOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/device/name/test-Random-Float-Device", - httpmock.NewStringResponder(404, DeviceDeleteFail)) - - err = deviceClient.Delete(context.TODO(), "test-Random-Float-Device", clients.DeleteOptions{}) - assert.NotNil(t, err) -} - -func Test_GetPropertyState(t *testing.T) { - - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-command:59882/api/v3/device/name/Random-Float-Device", - httpmock.NewStringResponder(200, DeviceCoreCommands)) - httpmock.RegisterResponder("GET", "http://edgex-core-command:59882/api/v3/device/name/Random-Float-Device/Float32", - httpmock.NewStringResponder(200, DeviceCommandResp)) - - var resp edgex_resp.DeviceResponse - - err := json.Unmarshal([]byte(DeviceMetadata), &resp) - assert.Nil(t, err) - - device := toKubeDevice(resp.Device, "default") - - _, err = deviceClient.GetPropertyState(context.TODO(), "Float32", &device, clients.GetOptions{}) - assert.Nil(t, err) -} - -func Test_ListPropertiesState(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-command:59882/api/v3/device/name/Random-Float-Device", - httpmock.NewStringResponder(200, DeviceCoreCommands)) - - var resp edgex_resp.DeviceResponse - - err := json.Unmarshal([]byte(DeviceMetadata), &resp) - assert.Nil(t, err) - - device := toKubeDevice(resp.Device, "default") - - _, _, err = deviceClient.ListPropertiesState(context.TODO(), &device, clients.ListOptions{}) - assert.Nil(t, err) -} - -func Test_UpdateDevice(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("PATCH", "http://edgex-core-metadata:59881/api/v3/device", - httpmock.NewStringResponder(207, DeviceUpdateSuccess)) - - var resp edgex_resp.DeviceResponse - - err := json.Unmarshal([]byte(DeviceMetadata), &resp) - assert.Nil(t, err) - - device := toKubeDevice(resp.Device, "default") - device.Spec.AdminState = "LOCKED" - - _, err = deviceClient.Update(context.TODO(), &device, clients.UpdateOptions{}) - assert.Nil(t, err) -} - -func Test_UpdatePropertyState(t *testing.T) { - httpmock.ActivateNonDefault(deviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-command:59882/api/v3/device/name/Random-Float-Device", - httpmock.NewStringResponder(200, DeviceCoreCommands)) - - httpmock.RegisterResponder("PUT", "http://edgex-core-command:59882/api/v3/device/name/Random-Float-Device/Float32", - httpmock.NewStringResponder(200, DeviceUpdateSuccess)) - var resp edgex_resp.DeviceResponse - err := json.Unmarshal([]byte(DeviceMetadata), &resp) - assert.Nil(t, err) - - device := toKubeDevice(resp.Device, "default") - device.Spec.DeviceProperties = map[string]iotv1alpha1.DesiredPropertyState{ - "Float32": { - Name: "Float32", - DesiredValue: "66.66", - }, - } - - err = deviceClient.UpdatePropertyState(context.TODO(), "Float32", &device, clients.UpdateOptions{}) - assert.Nil(t, err) -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client.go deleted file mode 100644 index 3e16e0e1c39..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client.go +++ /dev/null @@ -1,141 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v3 - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/go-resty/resty/v2" - "k8s.io/klog/v2" - - "github.com/openyurtio/openyurt/pkg/apis/iot/v1alpha1" - devcli "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -type EdgexDeviceProfile struct { - *resty.Client - CoreMetaAddr string -} - -func NewEdgexDeviceProfile(coreMetaAddr string) *EdgexDeviceProfile { - return &EdgexDeviceProfile{ - Client: resty.New(), - CoreMetaAddr: coreMetaAddr, - } -} - -// TODO: support label filtering -func getListDeviceProfileURL(address string, opts devcli.ListOptions) (string, error) { - url := fmt.Sprintf("http://%s%s/all?limit=-1", address, DeviceProfilePath) - return url, nil -} - -func (cdc *EdgexDeviceProfile) List(ctx context.Context, opts devcli.ListOptions) ([]v1alpha1.DeviceProfile, error) { - klog.V(5).Info("will list DeviceProfiles") - lp, err := getListDeviceProfileURL(cdc.CoreMetaAddr, opts) - if err != nil { - return nil, err - } - resp, err := cdc.R().EnableTrace().Get(lp) - if err != nil { - return nil, err - } - var mdpResp responses.MultiDeviceProfilesResponse - if err := json.Unmarshal(resp.Body(), &mdpResp); err != nil { - return nil, err - } - var deviceProfiles []v1alpha1.DeviceProfile - for _, dp := range mdpResp.Profiles { - deviceProfiles = append(deviceProfiles, toKubeDeviceProfile(&dp, opts.Namespace)) - } - return deviceProfiles, nil -} - -func (cdc *EdgexDeviceProfile) Get(ctx context.Context, name string, opts devcli.GetOptions) (*v1alpha1.DeviceProfile, error) { - klog.V(5).Infof("will get DeviceProfiles: %s", name) - var dpResp responses.DeviceProfileResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, DeviceProfilePath, name) - resp, err := cdc.R().Get(getURL) - if err != nil { - return nil, err - } - if resp.StatusCode() == http.StatusNotFound { - return nil, fmt.Errorf("DeviceProfile %s not found", name) - } - if err = json.Unmarshal(resp.Body(), &dpResp); err != nil { - return nil, err - } - kubedp := toKubeDeviceProfile(&dpResp.Profile, opts.Namespace) - return &kubedp, nil -} - -func (cdc *EdgexDeviceProfile) Create(ctx context.Context, deviceProfile *v1alpha1.DeviceProfile, opts devcli.CreateOptions) (*v1alpha1.DeviceProfile, error) { - dps := []*v1alpha1.DeviceProfile{deviceProfile} - req := makeEdgeXDeviceProfilesRequest(dps) - klog.V(5).Infof("will add the DeviceProfile: %s", deviceProfile.Name) - reqBody, err := json.Marshal(req) - if err != nil { - return nil, err - } - postURL := fmt.Sprintf("http://%s%s", cdc.CoreMetaAddr, DeviceProfilePath) - resp, err := cdc.R().SetBody(reqBody).Post(postURL) - if err != nil { - return nil, err - } - if resp.StatusCode() != http.StatusMultiStatus { - return nil, fmt.Errorf("create edgex deviceProfile err: %s", string(resp.Body())) // 假定 resp.Body() 存了 msg 信息 - } - var edgexResps []*common.BaseWithIdResponse - if err = json.Unmarshal(resp.Body(), &edgexResps); err != nil { - return nil, err - } - createdDeviceProfile := deviceProfile.DeepCopy() - if len(edgexResps) == 1 { - if edgexResps[0].StatusCode == http.StatusCreated { - createdDeviceProfile.Status.EdgeId = edgexResps[0].Id - createdDeviceProfile.Status.Synced = true - } else { - return nil, fmt.Errorf("create deviceprofile on edgex foundry failed, the response is : %s", resp.Body()) - } - } else { - return nil, fmt.Errorf("edgex BaseWithIdResponse count mismatch DeviceProfile count, the response is : %s", resp.Body()) - } - return createdDeviceProfile, err -} - -// TODO: edgex does not support update DeviceProfile -func (cdc *EdgexDeviceProfile) Update(ctx context.Context, deviceProfile *v1alpha1.DeviceProfile, opts devcli.UpdateOptions) (*v1alpha1.DeviceProfile, error) { - return nil, nil -} - -func (cdc *EdgexDeviceProfile) Delete(ctx context.Context, name string, opts devcli.DeleteOptions) error { - klog.V(5).Infof("will delete the DeviceProfile: %s", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", cdc.CoreMetaAddr, DeviceProfilePath, name) - resp, err := cdc.R().Delete(delURL) - if err != nil { - return err - } - if resp.StatusCode() != http.StatusOK { - return fmt.Errorf("delete edgex deviceProfile err: %s", string(resp.Body())) // 假定 resp.Body() 存了 msg 信息 - } - return nil -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client_test.go deleted file mode 100644 index c0b3c4babcb..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceprofile_client_test.go +++ /dev/null @@ -1,107 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package v3 - -import ( - "context" - "encoding/json" - "testing" - - edgex_resp "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/jarcoal/httpmock" - "github.com/stretchr/testify/assert" - - "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -const ( - DeviceProfileListMetaData = `{"apiVersion":"v3","statusCode":200,"totalCount":5,"profiles":[{"created":1661829206499,"modified":1661829206499,"id":"cf624c1f-c93a-48c0-b327-b00c7dc171f1","name":"Random-Binary-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"Generate random binary value","name":"Binary","isHidden":false,"tag":"","properties":{"valueType":"Binary","readWrite":"R","units":"","defaultValue":"","assertion":"","mediaType":"random"},"attributes":null}],"deviceCommands":[]},{"created":1661829206501,"modified":1661829206501,"id":"adeafefa-2d11-4eee-8fe9-a4742f85f7fb","name":"Random-UnsignedInteger-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint8","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint16","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint32","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint64","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint8 value","name":"Uint8","isHidden":false,"tag":"","properties":{"valueType":"Uint8","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint16 value","name":"Uint16","isHidden":false,"tag":"","properties":{"valueType":"Uint16","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint32 value","name":"Uint32","isHidden":false,"tag":"","properties":{"valueType":"Uint32","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint64 value","name":"Uint64","isHidden":false,"tag":"","properties":{"valueType":"Uint64","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint8Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint16Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint32Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Uint64Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint8 array value","name":"Uint8Array","isHidden":false,"tag":"","properties":{"valueType":"Uint8Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint16 array value","name":"Uint16Array","isHidden":false,"tag":"","properties":{"valueType":"Uint16Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint32 array value","name":"Uint32Array","isHidden":false,"tag":"","properties":{"valueType":"Uint32Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random uint64 array value","name":"Uint64Array","isHidden":false,"tag":"","properties":{"valueType":"Uint64Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null}],"deviceCommands":[{"name":"WriteUint8Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint8","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint8","defaultValue":"false","mappings":null}]},{"name":"WriteUint16Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint16","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint16","defaultValue":"false","mappings":null}]},{"name":"WriteUint32Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint32","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint32","defaultValue":"false","mappings":null}]},{"name":"WriteUint64Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint64","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint64","defaultValue":"false","mappings":null}]},{"name":"WriteUint8ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint8Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint8Array","defaultValue":"false","mappings":null}]},{"name":"WriteUint16ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint16Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint16Array","defaultValue":"false","mappings":null}]},{"name":"WriteUint32ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint32Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint32Array","defaultValue":"false","mappings":null}]},{"name":"WriteUint64ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Uint64Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Uint64Array","defaultValue":"false","mappings":null}]}]},{"created":1661829206500,"modified":1661829206500,"id":"67f4a5a1-06e6-4051-b71d-655ec5dd4eb2","name":"Random-Integer-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int8","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int16","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int32","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int64","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int8 value","name":"Int8","isHidden":false,"tag":"","properties":{"valueType":"Int8","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int16 value","name":"Int16","isHidden":false,"tag":"","properties":{"valueType":"Int16","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int32 value","name":"Int32","isHidden":false,"tag":"","properties":{"valueType":"Int32","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int64 value","name":"Int64","isHidden":false,"tag":"","properties":{"valueType":"Int64","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int8Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int16Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int32Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Int64Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int8 array value","name":"Int8Array","isHidden":false,"tag":"","properties":{"valueType":"Int8Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int16 array value","name":"Int16Array","isHidden":false,"tag":"","properties":{"valueType":"Int16Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int32 array value","name":"Int32Array","isHidden":false,"tag":"","properties":{"valueType":"Int32Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random int64 array value","name":"Int64Array","isHidden":false,"tag":"","properties":{"valueType":"Int64Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null}],"deviceCommands":[{"name":"WriteInt8Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int8","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int8","defaultValue":"false","mappings":null}]},{"name":"WriteInt16Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int16","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int16","defaultValue":"false","mappings":null}]},{"name":"WriteInt32Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int32","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int32","defaultValue":"false","mappings":null}]},{"name":"WriteInt64Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int64","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int64","defaultValue":"false","mappings":null}]},{"name":"WriteInt8ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int8Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int8Array","defaultValue":"false","mappings":null}]},{"name":"WriteInt16ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int16Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int16Array","defaultValue":"false","mappings":null}]},{"name":"WriteInt32ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int32Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int32Array","defaultValue":"false","mappings":null}]},{"name":"WriteInt64ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Int64Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Int64Array","defaultValue":"false","mappings":null}]}]},{"created":1661829206500,"modified":1661829206500,"id":"30b8448f-0532-44fb-aed7-5fe4bca16f9a","name":"Random-Float-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Float32","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Float64","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random float32 value","name":"Float32","isHidden":false,"tag":"","properties":{"valueType":"Float32","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random float64 value","name":"Float64","isHidden":false,"tag":"","properties":{"valueType":"Float64","readWrite":"RW","units":"","defaultValue":"0","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Float32Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Float64Array","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random float32 array value","name":"Float32Array","isHidden":false,"tag":"","properties":{"valueType":"Float32Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random float64 array value","name":"Float64Array","isHidden":false,"tag":"","properties":{"valueType":"Float64Array","readWrite":"RW","units":"","defaultValue":"[0]","assertion":"","mediaType":""},"attributes":null}],"deviceCommands":[{"name":"WriteFloat32Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Float32","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Float32","defaultValue":"false","mappings":null}]},{"name":"WriteFloat64Value","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Float64","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Float64","defaultValue":"false","mappings":null}]},{"name":"WriteFloat32ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Float32Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Float32Array","defaultValue":"false","mappings":null}]},{"name":"WriteFloat64ArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Float64Array","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Float64Array","defaultValue":"false","mappings":null}]}]},{"created":1661829206499,"modified":1661829206499,"id":"01dfe04d-f361-41fd-b1c4-7ca0718f461a","name":"Random-Boolean-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Bool","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random boolean value","name":"Bool","isHidden":false,"tag":"","properties":{"valueType":"Bool","readWrite":"RW","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_BoolArray","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random boolean array value","name":"BoolArray","isHidden":false,"tag":"","properties":{"valueType":"BoolArray","readWrite":"RW","units":"","defaultValue":"[true]","assertion":"","mediaType":""},"attributes":null}],"deviceCommands":[{"name":"WriteBoolValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Bool","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Bool","defaultValue":"false","mappings":null}]},{"name":"WriteBoolArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"BoolArray","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_BoolArray","defaultValue":"false","mappings":null}]}]}]}` - DeviceProfileMetaData = `{"apiVersion":"v3","statusCode":200,"profile":{"created":1661829206499,"modified":1661829206499,"id":"01dfe04d-f361-41fd-b1c4-7ca0718f461a","name":"Random-Boolean-Device","manufacturer":"IOTech","description":"Example of Device-Virtual","model":"Device-Virtual-01","labels":["device-virtual-example"],"deviceResources":[{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_Bool","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random boolean value","name":"Bool","isHidden":false,"tag":"","properties":{"valueType":"Bool","readWrite":"RW","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"used to decide whether to re-generate a random value","name":"EnableRandomization_BoolArray","isHidden":true,"tag":"","properties":{"valueType":"Bool","readWrite":"W","units":"","defaultValue":"true","assertion":"","mediaType":""},"attributes":null},{"description":"Generate random boolean array value","name":"BoolArray","isHidden":false,"tag":"","properties":{"valueType":"BoolArray","readWrite":"RW","units":"","defaultValue":"[true]","assertion":"","mediaType":""},"attributes":null}],"deviceCommands":[{"name":"WriteBoolValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"Bool","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_Bool","defaultValue":"false","mappings":null}]},{"name":"WriteBoolArrayValue","isHidden":false,"readWrite":"W","resourceOperations":[{"deviceResource":"BoolArray","defaultValue":"","mappings":null},{"deviceResource":"EnableRandomization_BoolArray","defaultValue":"false","mappings":null}]}]}}` - - ProfileCreateSuccess = `[{"apiVersion":"v3","statusCode":201,"id":"a583b97d-7c4d-4b7c-8b93-51da9e68518c"}]` - ProfileCreateFail = `[{"apiVersion":"v3","message":"device profile name test-Random-Boolean-Device exists","statusCode":409}]` - - ProfileDeleteSuccess = `{"apiVersion":"v3","statusCode":200}` - ProfileDeleteFail = `{"apiVersion":"v3","message":"fail to delete the device profile with name test-Random-Boolean-Device","statusCode":404}` -) - -var profileClient = NewEdgexDeviceProfile("edgex-core-metadata:59881") - -func Test_ListProfile(t *testing.T) { - - httpmock.ActivateNonDefault(profileClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/deviceprofile/all?limit=-1", - httpmock.NewStringResponder(200, DeviceProfileListMetaData)) - profiles, err := profileClient.List(context.TODO(), clients.ListOptions{Namespace: "default"}) - assert.Nil(t, err) - - assert.Equal(t, 5, len(profiles)) -} - -func Test_GetProfile(T *testing.T) { - httpmock.ActivateNonDefault(profileClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/deviceprofile/name/Random-Boolean-Device", - httpmock.NewStringResponder(200, DeviceProfileMetaData)) - - _, err := profileClient.Get(context.TODO(), "Random-Boolean-Device", clients.GetOptions{Namespace: "default"}) - assert.Nil(T, err) -} - -func Test_CreateProfile(t *testing.T) { - httpmock.ActivateNonDefault(profileClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/deviceprofile", - httpmock.NewStringResponder(207, ProfileCreateSuccess)) - - var resp edgex_resp.DeviceProfileResponse - - err := json.Unmarshal([]byte(DeviceProfileMetaData), &resp) - assert.Nil(t, err) - - profile := toKubeDeviceProfile(&resp.Profile, "default") - profile.Name = "test-Random-Boolean-Device" - - _, err = profileClient.Create(context.TODO(), &profile, clients.CreateOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/deviceprofile", - httpmock.NewStringResponder(207, ProfileCreateFail)) - - _, err = profileClient.Create(context.TODO(), &profile, clients.CreateOptions{}) - assert.NotNil(t, err) -} - -func Test_DeleteProfile(t *testing.T) { - httpmock.ActivateNonDefault(profileClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/deviceprofile/name/test-Random-Boolean-Device", - httpmock.NewStringResponder(200, ProfileDeleteSuccess)) - - err := profileClient.Delete(context.TODO(), "test-Random-Boolean-Device", clients.DeleteOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/deviceprofile/name/test-Random-Boolean-Device", - httpmock.NewStringResponder(404, ProfileDeleteFail)) - - err = profileClient.Delete(context.TODO(), "test-Random-Boolean-Device", clients.DeleteOptions{}) - assert.NotNil(t, err) -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client.go deleted file mode 100644 index b6bf060494c..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client.go +++ /dev/null @@ -1,166 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v3 - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/go-resty/resty/v2" - "k8s.io/klog/v2" - - "github.com/openyurtio/openyurt/pkg/apis/iot/v1alpha1" - edgeCli "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -type EdgexDeviceServiceClient struct { - *resty.Client - CoreMetaAddr string -} - -func NewEdgexDeviceServiceClient(coreMetaAddr string) *EdgexDeviceServiceClient { - return &EdgexDeviceServiceClient{ - Client: resty.New(), - CoreMetaAddr: coreMetaAddr, - } -} - -// Create function sends a POST request to EdgeX to add a new deviceService -func (eds *EdgexDeviceServiceClient) Create(ctx context.Context, deviceService *v1alpha1.DeviceService, options edgeCli.CreateOptions) (*v1alpha1.DeviceService, error) { - dss := []*v1alpha1.DeviceService{deviceService} - req := makeEdgeXDeviceService(dss) - klog.V(5).InfoS("will add the DeviceServices", "DeviceService", deviceService.Name) - jsonBody, err := json.Marshal(req) - if err != nil { - return nil, err - } - postPath := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, DeviceServicePath) - resp, err := eds.R(). - SetBody(jsonBody).Post(postPath) - if err != nil { - return nil, err - } else if resp.StatusCode() != http.StatusMultiStatus { - return nil, fmt.Errorf("create DeviceService on edgex foundry failed, the response is : %s", resp.Body()) - } - - var edgexResps []*common.BaseWithIdResponse - if err = json.Unmarshal(resp.Body(), &edgexResps); err != nil { - return nil, err - } - createdDeviceService := deviceService.DeepCopy() - if len(edgexResps) == 1 { - if edgexResps[0].StatusCode == http.StatusCreated { - createdDeviceService.Status.EdgeId = edgexResps[0].Id - createdDeviceService.Status.Synced = true - } else { - return nil, fmt.Errorf("create DeviceService on edgex foundry failed, the response is : %s", resp.Body()) - } - } else { - return nil, fmt.Errorf("edgex BaseWithIdResponse count mismatch DeviceService count, the response is : %s", resp.Body()) - } - return createdDeviceService, err -} - -// Delete function sends a request to EdgeX to delete a deviceService -func (eds *EdgexDeviceServiceClient) Delete(ctx context.Context, name string, option edgeCli.DeleteOptions) error { - klog.V(5).InfoS("will delete the DeviceService", "DeviceService", name) - delURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, DeviceServicePath, name) - resp, err := eds.R().Delete(delURL) - if err != nil { - return err - } - if resp.StatusCode() != http.StatusOK { - return fmt.Errorf("delete edgex deviceservice err: %s", string(resp.Body())) - } - return nil -} - -// Update is used to set the admin or operating state of the deviceService by unique name of the deviceService. -// TODO support to update other fields -func (eds *EdgexDeviceServiceClient) Update(ctx context.Context, ds *v1alpha1.DeviceService, options edgeCli.UpdateOptions) (*v1alpha1.DeviceService, error) { - patchURL := fmt.Sprintf("http://%s%s", eds.CoreMetaAddr, DeviceServicePath) - if ds == nil { - return nil, nil - } - - if ds.Status.EdgeId == "" { - return nil, fmt.Errorf("failed to update deviceservice %s with empty edgex id", ds.Name) - } - edgeDs := toEdgexDeviceService(ds) - edgeDs.Id = ds.Status.EdgeId - dsJson, err := json.Marshal(&edgeDs) - if err != nil { - return nil, err - } - resp, err := eds.R(). - SetBody(dsJson).Patch(patchURL) - if err != nil { - return nil, err - } - - if resp.StatusCode() == http.StatusOK || resp.StatusCode() == http.StatusMultiStatus { - return ds, nil - } else { - return nil, fmt.Errorf("request to patch deviceservice failed, errcode:%d", resp.StatusCode()) - } -} - -// Get is used to query the deviceService information corresponding to the deviceService name -func (eds *EdgexDeviceServiceClient) Get(ctx context.Context, name string, options edgeCli.GetOptions) (*v1alpha1.DeviceService, error) { - klog.V(5).InfoS("will get DeviceServices", "DeviceService", name) - var dsResp responses.DeviceServiceResponse - getURL := fmt.Sprintf("http://%s%s/name/%s", eds.CoreMetaAddr, DeviceServicePath, name) - resp, err := eds.R().Get(getURL) - if err != nil { - return nil, err - } - if resp.StatusCode() == http.StatusNotFound { - return nil, fmt.Errorf("deviceservice %s not found", name) - } - err = json.Unmarshal(resp.Body(), &dsResp) - if err != nil { - return nil, err - } - ds := toKubeDeviceService(dsResp.Service, options.Namespace) - return &ds, nil -} - -// List is used to get all deviceService objects on edge platform -// The Hanoi version currently supports only a single label and does not support other filters -func (eds *EdgexDeviceServiceClient) List(ctx context.Context, options edgeCli.ListOptions) ([]v1alpha1.DeviceService, error) { - klog.V(5).Info("will list DeviceServices") - lp := fmt.Sprintf("http://%s%s/all?limit=-1", eds.CoreMetaAddr, DeviceServicePath) - resp, err := eds.R(). - EnableTrace(). - Get(lp) - if err != nil { - return nil, err - } - var mdsResponse responses.MultiDeviceServicesResponse - if err := json.Unmarshal(resp.Body(), &mdsResponse); err != nil { - return nil, err - } - var res []v1alpha1.DeviceService - for _, ds := range mdsResponse.Services { - res = append(res, toKubeDeviceService(ds, options.Namespace)) - } - return res, nil -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go deleted file mode 100644 index 0fa72a094fe..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package v3 - -import ( - "context" - "encoding/json" - "testing" - - edgex_resp "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses" - "github.com/jarcoal/httpmock" - "github.com/stretchr/testify/assert" - - "github.com/openyurtio/openyurt/pkg/yurtiotdock/clients" -) - -const ( - DeviceServiceListMetaData = `{"apiVersion":"v3","statusCode":200,"totalCount":1,"services":[{"created":1661829206490,"modified":1661850999190,"id":"74516e96-973d-4cad-bad1-afd4b3a8ea46","name":"device-virtual","baseAddress":"http://edgex-device-virtual:59900","adminState":"UNLOCKED"}]}` - DeviceServiceMetaData = `{"apiVersion":"v3","statusCode":200,"service":{"created":1661829206490,"modified":1661850999190,"id":"74516e96-973d-4cad-bad1-afd4b3a8ea46","name":"device-virtual","baseAddress":"http://edgex-device-virtual:59900","adminState":"UNLOCKED"}}` - ServiceCreateSuccess = `[{"apiVersion":"v3","statusCode":201,"id":"a583b97d-7c4d-4b7c-8b93-51da9e68518c"}]` - ServiceCreateFail = `[{"apiVersion":"v3","message":"device service name test-device-virtual exists","statusCode":409}]` - - ServiceDeleteSuccess = `{"apiVersion":"v3","statusCode":200}` - ServiceDeleteFail = `{"apiVersion":"v3","message":"fail to delete the device profile with name test-Random-Boolean-Device","statusCode":404}` - - ServiceUpdateSuccess = `[{"apiVersion":"v3","statusCode":200}]` - ServiceUpdateFail = `[{"apiVersion":"v3","message":"fail to query object *models.DeviceService, because id: md|ds:01dfe04d-f361-41fd-b1c4-7ca0718f461a doesn't exist in the database","statusCode":404}]` -) - -var serviceClient = NewEdgexDeviceServiceClient("edgex-core-metadata:59881") - -func Test_GetService(t *testing.T) { - httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/deviceservice/name/device-virtual", - httpmock.NewStringResponder(200, DeviceServiceMetaData)) - - _, err := serviceClient.Get(context.TODO(), "device-virtual", clients.GetOptions{Namespace: "default"}) - assert.Nil(t, err) -} - -func Test_ListService(t *testing.T) { - httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("GET", "http://edgex-core-metadata:59881/api/v3/deviceservice/all?limit=-1", - httpmock.NewStringResponder(200, DeviceServiceListMetaData)) - - services, err := serviceClient.List(context.TODO(), clients.ListOptions{}) - assert.Nil(t, err) - assert.Equal(t, 1, len(services)) -} - -func Test_CreateService(t *testing.T) { - httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/deviceservice", - httpmock.NewStringResponder(207, ServiceCreateSuccess)) - - var resp edgex_resp.DeviceServiceResponse - - err := json.Unmarshal([]byte(DeviceServiceMetaData), &resp) - assert.Nil(t, err) - - service := toKubeDeviceService(resp.Service, "default") - service.Name = "test-device-virtual" - - _, err = serviceClient.Create(context.TODO(), &service, clients.CreateOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("POST", "http://edgex-core-metadata:59881/api/v3/deviceservice", - httpmock.NewStringResponder(207, ServiceCreateFail)) -} - -func Test_DeleteService(t *testing.T) { - httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/deviceservice/name/test-device-virtual", - httpmock.NewStringResponder(200, ServiceDeleteSuccess)) - - err := serviceClient.Delete(context.TODO(), "test-device-virtual", clients.DeleteOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("DELETE", "http://edgex-core-metadata:59881/api/v3/deviceservice/name/test-device-virtual", - httpmock.NewStringResponder(404, ServiceDeleteFail)) - - err = serviceClient.Delete(context.TODO(), "test-device-virtual", clients.DeleteOptions{}) - assert.NotNil(t, err) -} - -func Test_UpdateService(t *testing.T) { - httpmock.ActivateNonDefault(serviceClient.Client.GetClient()) - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("PATCH", "http://edgex-core-metadata:59881/api/v3/deviceservice", - httpmock.NewStringResponder(200, ServiceUpdateSuccess)) - var resp edgex_resp.DeviceServiceResponse - - err := json.Unmarshal([]byte(DeviceServiceMetaData), &resp) - assert.Nil(t, err) - - service := toKubeDeviceService(resp.Service, "default") - _, err = serviceClient.Update(context.TODO(), &service, clients.UpdateOptions{}) - assert.Nil(t, err) - - httpmock.RegisterResponder("PATCH", "http://edgex-core-metadata:59881/api/v3/deviceservice", - httpmock.NewStringResponder(404, ServiceUpdateFail)) - - _, err = serviceClient.Update(context.TODO(), &service, clients.UpdateOptions{}) - assert.NotNil(t, err) -} diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/util.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/util.go deleted file mode 100644 index 60c4fd05662..00000000000 --- a/pkg/yurtiotdock/clients/edgex-foundry/v3/util.go +++ /dev/null @@ -1,472 +0,0 @@ -/* -Copyright 2023 The OpenYurt Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v3 - -import ( - "fmt" - "strings" - - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common" - "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" - "github.com/edgexfoundry/go-mod-core-contracts/v3/models" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - iotv1alpha1 "github.com/openyurtio/openyurt/pkg/apis/iot/v1alpha1" - util "github.com/openyurtio/openyurt/pkg/yurtiotdock/controllers/util" -) - -const ( - EdgeXObjectName = "yurt-iot-dock/edgex-object.name" - DeviceServicePath = "/api/v3/deviceservice" - DeviceProfilePath = "/api/v3/deviceprofile" - DevicePath = "/api/v3/device" - CommandResponsePath = "/api/v3/device" - - APIVersionV3 = "v3" -) - -type ClientURL struct { - Host string - Port int -} - -func getEdgeXName(provider metav1.Object) string { - var actualDeviceName string - if _, ok := provider.GetLabels()[EdgeXObjectName]; ok { - actualDeviceName = provider.GetLabels()[EdgeXObjectName] - } else { - actualDeviceName = provider.GetName() - } - return actualDeviceName -} - -func toEdgexDeviceService(ds *iotv1alpha1.DeviceService) dtos.DeviceService { - return dtos.DeviceService{ - Description: ds.Spec.Description, - Name: getEdgeXName(ds), - Labels: ds.Spec.Labels, - AdminState: string(ds.Spec.AdminState), - BaseAddress: ds.Spec.BaseAddress, - // TODO: Metric LastConnected / LastReported - } -} - -func toEdgeXDeviceResourceSlice(drs []iotv1alpha1.DeviceResource) []dtos.DeviceResource { - var ret []dtos.DeviceResource - for _, dr := range drs { - ret = append(ret, toEdgeXDeviceResource(dr)) - } - return ret -} - -func toEdgeXDeviceResource(dr iotv1alpha1.DeviceResource) dtos.DeviceResource { - genericAttrs := make(map[string]interface{}) - for k, v := range dr.Attributes { - genericAttrs[k] = v - } - - return dtos.DeviceResource{ - Description: dr.Description, - Name: dr.Name, - // Tag: dr.Tag, - Properties: toEdgeXProfileProperty(dr.Properties), - Attributes: genericAttrs, - } -} - -func toEdgeXProfileProperty(pp iotv1alpha1.ResourceProperties) dtos.ResourceProperties { - return dtos.ResourceProperties{ - ReadWrite: pp.ReadWrite, - Minimum: util.StrToFloat(pp.Minimum), - Maximum: util.StrToFloat(pp.Maximum), - DefaultValue: pp.DefaultValue, - Mask: util.StrToUint(pp.Mask), - Shift: util.StrToInt(pp.Shift), - Scale: util.StrToFloat(pp.Scale), - Offset: util.StrToFloat(pp.Offset), - Base: util.StrToFloat(pp.Base), - Assertion: pp.Assertion, - MediaType: pp.MediaType, - Units: pp.Units, - ValueType: pp.ValueType, - } -} - -func toKubeDeviceService(ds dtos.DeviceService, namespace string) iotv1alpha1.DeviceService { - return iotv1alpha1.DeviceService{ - ObjectMeta: metav1.ObjectMeta{ - Name: toKubeName(ds.Name), - Namespace: namespace, - Labels: map[string]string{ - EdgeXObjectName: ds.Name, - }, - }, - Spec: iotv1alpha1.DeviceServiceSpec{ - Description: ds.Description, - Labels: ds.Labels, - AdminState: iotv1alpha1.AdminState(ds.AdminState), - BaseAddress: ds.BaseAddress, - }, - Status: iotv1alpha1.DeviceServiceStatus{ - EdgeId: ds.Id, - AdminState: iotv1alpha1.AdminState(ds.AdminState), - // TODO: Metric LastConnected / LastReported - }, - } -} - -func toEdgeXDevice(d *iotv1alpha1.Device) dtos.Device { - md := dtos.Device{ - Description: d.Spec.Description, - Name: getEdgeXName(d), - AdminState: string(toEdgeXAdminState(d.Spec.AdminState)), - OperatingState: string(toEdgeXOperatingState(d.Spec.OperatingState)), - Protocols: toEdgeXProtocols(d.Spec.Protocols), - // TODO: Metric LastConnected / LastReported - Labels: d.Spec.Labels, - Location: d.Spec.Location, - ServiceName: d.Spec.Service, - ProfileName: d.Spec.Profile, - } - if d.Status.EdgeId != "" { - md.Id = d.Status.EdgeId - } - return md -} - -func toEdgeXUpdateDevice(d *iotv1alpha1.Device) dtos.UpdateDevice { - adminState := string(toEdgeXAdminState(d.Spec.AdminState)) - operationState := string(toEdgeXOperatingState(d.Spec.OperatingState)) - md := dtos.UpdateDevice{ - Description: &d.Spec.Description, - Name: &d.Name, - AdminState: &adminState, - OperatingState: &operationState, - Protocols: toEdgeXProtocols(d.Spec.Protocols), - Labels: d.Spec.Labels, - Location: d.Spec.Location, - ServiceName: &d.Spec.Service, - ProfileName: &d.Spec.Profile, - // TODO: Metric LastConnected / LastReported - } - if d.Status.EdgeId != "" { - md.Id = &d.Status.EdgeId - } - return md -} - -func toEdgeXProtocols( - pps map[string]iotv1alpha1.ProtocolProperties) map[string]dtos.ProtocolProperties { - ret := make(map[string]dtos.ProtocolProperties, len(pps)) - for k, v := range pps { - propMap := make(map[string]interface{}) - for key, value := range v { - propMap[key] = value - } - ret[k] = dtos.ProtocolProperties(propMap) - } - return ret -} - -func toEdgeXAdminState(as iotv1alpha1.AdminState) models.AdminState { - if as == iotv1alpha1.Locked { - return models.Locked - } - return models.Unlocked -} - -func toEdgeXOperatingState(os iotv1alpha1.OperatingState) models.OperatingState { - if os == iotv1alpha1.Up { - return models.Up - } else if os == iotv1alpha1.Down { - return models.Down - } - return models.Unknown -} - -// toKubeDevice serialize the EdgeX Device to the corresponding Kubernetes Device -func toKubeDevice(ed dtos.Device, namespace string) iotv1alpha1.Device { - var loc string - if ed.Location != nil { - loc = ed.Location.(string) - } - return iotv1alpha1.Device{ - ObjectMeta: metav1.ObjectMeta{ - Name: toKubeName(ed.Name), - Namespace: namespace, - Labels: map[string]string{ - EdgeXObjectName: ed.Name, - }, - }, - Spec: iotv1alpha1.DeviceSpec{ - Description: ed.Description, - AdminState: iotv1alpha1.AdminState(ed.AdminState), - OperatingState: iotv1alpha1.OperatingState(ed.OperatingState), - Protocols: toKubeProtocols(ed.Protocols), - Labels: ed.Labels, - Location: loc, - Service: ed.ServiceName, - Profile: ed.ProfileName, - // TODO: Notify - }, - Status: iotv1alpha1.DeviceStatus{ - // TODO: Metric LastConnected / LastReported - Synced: true, - EdgeId: ed.Id, - AdminState: iotv1alpha1.AdminState(ed.AdminState), - OperatingState: iotv1alpha1.OperatingState(ed.OperatingState), - }, - } -} - -// toKubeProtocols serialize the EdgeX ProtocolProperties to the corresponding -// Kubernetes OperatingState -func toKubeProtocols( - eps map[string]dtos.ProtocolProperties) map[string]iotv1alpha1.ProtocolProperties { - ret := map[string]iotv1alpha1.ProtocolProperties{} - for k, v := range eps { - propMap := make(map[string]string) - for key, value := range v { - switch asserted := value.(type) { - case string: - propMap[key] = asserted - continue - case int: - propMap[key] = fmt.Sprintf("%d", asserted) - continue - case float64: - propMap[key] = fmt.Sprintf("%f", asserted) - continue - case fmt.Stringer: - propMap[key] = asserted.String() - continue - } - } - ret[k] = iotv1alpha1.ProtocolProperties(propMap) - } - return ret -} - -// toKubeDeviceProfile create DeviceProfile in cloud according to devicProfile in edge -func toKubeDeviceProfile(dp *dtos.DeviceProfile, namespace string) iotv1alpha1.DeviceProfile { - return iotv1alpha1.DeviceProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: toKubeName(dp.Name), - Namespace: namespace, - Labels: map[string]string{ - EdgeXObjectName: dp.Name, - }, - }, - Spec: iotv1alpha1.DeviceProfileSpec{ - Description: dp.Description, - Manufacturer: dp.Manufacturer, - Model: dp.Model, - Labels: dp.Labels, - DeviceResources: toKubeDeviceResources(dp.DeviceResources), - DeviceCommands: toKubeDeviceCommand(dp.DeviceCommands), - }, - Status: iotv1alpha1.DeviceProfileStatus{ - EdgeId: dp.Id, - Synced: true, - }, - } -} - -func toKubeDeviceCommand(dcs []dtos.DeviceCommand) []iotv1alpha1.DeviceCommand { - var ret []iotv1alpha1.DeviceCommand - for _, dc := range dcs { - ret = append(ret, iotv1alpha1.DeviceCommand{ - Name: dc.Name, - ReadWrite: dc.ReadWrite, - IsHidden: dc.IsHidden, - ResourceOperations: toKubeResourceOperations(dc.ResourceOperations), - }) - } - return ret -} - -func toEdgeXDeviceCommand(dcs []iotv1alpha1.DeviceCommand) []dtos.DeviceCommand { - var ret []dtos.DeviceCommand - for _, dc := range dcs { - ret = append(ret, dtos.DeviceCommand{ - Name: dc.Name, - ReadWrite: dc.ReadWrite, - IsHidden: dc.IsHidden, - ResourceOperations: toEdgeXResourceOperations(dc.ResourceOperations), - }) - } - return ret -} - -func toKubeResourceOperations(ros []dtos.ResourceOperation) []iotv1alpha1.ResourceOperation { - var ret []iotv1alpha1.ResourceOperation - for _, ro := range ros { - ret = append(ret, iotv1alpha1.ResourceOperation{ - DeviceResource: ro.DeviceResource, - Mappings: ro.Mappings, - DefaultValue: ro.DefaultValue, - }) - } - return ret -} - -func toEdgeXResourceOperations(ros []iotv1alpha1.ResourceOperation) []dtos.ResourceOperation { - var ret []dtos.ResourceOperation - for _, ro := range ros { - ret = append(ret, dtos.ResourceOperation{ - DeviceResource: ro.DeviceResource, - Mappings: ro.Mappings, - DefaultValue: ro.DefaultValue, - }) - } - return ret -} - -func toKubeDeviceResources(drs []dtos.DeviceResource) []iotv1alpha1.DeviceResource { - var ret []iotv1alpha1.DeviceResource - for _, dr := range drs { - ret = append(ret, toKubeDeviceResource(dr)) - } - return ret -} - -func toKubeDeviceResource(dr dtos.DeviceResource) iotv1alpha1.DeviceResource { - concreteAttrs := make(map[string]string) - for k, v := range dr.Attributes { - switch asserted := v.(type) { - case string: - concreteAttrs[k] = asserted - continue - case int: - concreteAttrs[k] = fmt.Sprintf("%d", asserted) - continue - case float64: - concreteAttrs[k] = fmt.Sprintf("%f", asserted) - continue - case fmt.Stringer: - concreteAttrs[k] = asserted.String() - continue - } - } - - return iotv1alpha1.DeviceResource{ - Description: dr.Description, - Name: dr.Name, - // Tag: dr.Tag, - IsHidden: dr.IsHidden, - Properties: toKubeProfileProperty(dr.Properties), - Attributes: concreteAttrs, - } -} - -func toKubeProfileProperty(rp dtos.ResourceProperties) iotv1alpha1.ResourceProperties { - return iotv1alpha1.ResourceProperties{ - ValueType: rp.ValueType, - ReadWrite: rp.ReadWrite, - Minimum: util.FloatToStr(rp.Minimum), - Maximum: util.FloatToStr(rp.Maximum), - DefaultValue: rp.DefaultValue, - Mask: util.UintToStr(rp.Mask), - Shift: util.IntToStr(rp.Shift), - Scale: util.FloatToStr(rp.Scale), - Offset: util.FloatToStr(rp.Offset), - Base: util.FloatToStr(rp.Base), - Assertion: rp.Assertion, - MediaType: rp.MediaType, - Units: rp.Units, - } -} - -// toEdgeXDeviceProfile create DeviceProfile in edge according to devicProfile in cloud -func toEdgeXDeviceProfile(dp *iotv1alpha1.DeviceProfile) dtos.DeviceProfile { - return dtos.DeviceProfile{ - DeviceProfileBasicInfo: dtos.DeviceProfileBasicInfo{ - Description: dp.Spec.Description, - Name: getEdgeXName(dp), - Manufacturer: dp.Spec.Manufacturer, - Model: dp.Spec.Model, - Labels: dp.Spec.Labels, - }, - DeviceResources: toEdgeXDeviceResourceSlice(dp.Spec.DeviceResources), - DeviceCommands: toEdgeXDeviceCommand(dp.Spec.DeviceCommands), - } -} - -func makeEdgeXDeviceProfilesRequest(dps []*iotv1alpha1.DeviceProfile) []*requests.DeviceProfileRequest { - var req []*requests.DeviceProfileRequest - for _, dp := range dps { - req = append(req, &requests.DeviceProfileRequest{ - BaseRequest: common.BaseRequest{ - Versionable: common.Versionable{ - ApiVersion: APIVersionV3, - }, - }, - Profile: toEdgeXDeviceProfile(dp), - }) - } - return req -} - -func makeEdgeXDeviceUpdateRequest(devs []*iotv1alpha1.Device) []*requests.UpdateDeviceRequest { - var req []*requests.UpdateDeviceRequest - for _, dev := range devs { - req = append(req, &requests.UpdateDeviceRequest{ - BaseRequest: common.BaseRequest{ - Versionable: common.Versionable{ - ApiVersion: APIVersionV3, - }, - }, - Device: toEdgeXUpdateDevice(dev), - }) - } - return req -} - -func makeEdgeXDeviceRequest(devs []*iotv1alpha1.Device) []*requests.AddDeviceRequest { - var req []*requests.AddDeviceRequest - for _, dev := range devs { - req = append(req, &requests.AddDeviceRequest{ - BaseRequest: common.BaseRequest{ - Versionable: common.Versionable{ - ApiVersion: APIVersionV3, - }, - }, - Device: toEdgeXDevice(dev), - }) - } - return req -} - -func makeEdgeXDeviceService(dss []*iotv1alpha1.DeviceService) []*requests.AddDeviceServiceRequest { - var req []*requests.AddDeviceServiceRequest - for _, ds := range dss { - req = append(req, &requests.AddDeviceServiceRequest{ - BaseRequest: common.BaseRequest{ - Versionable: common.Versionable{ - ApiVersion: APIVersionV3, - }, - }, - Service: toEdgexDeviceService(ds), - }) - } - return req -} - -func toKubeName(edgexName string) string { - return strings.ReplaceAll(strings.ToLower(edgexName), "_", "-") -}