Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

CLI: Update Detector Configurations #233

Merged
merged 7 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions cli/cmd/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
* http://www.apache.org/licenses/LICENSE-2.0
* or in the "license" file accompanying this file. This file 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 cmd

import (
handler "esad/internal/handler/ad"
"fmt"

"github.com/spf13/cobra"
)

const (
commandUpdate = "update"
flagForce = "force"
flagStart = "start"
)

//updateCmd updates detectors based on file configuration
var updateCmd = &cobra.Command{
Use: commandUpdate + " [list of file-path] [flags]",
Short: "Updates detectors based on configurations",
Long: `Updates detectors based on configurations specified by file path`,
Run: func(cmd *cobra.Command, args []string) {
//If no args, display usage
if len(args) < 1 {
if err := cmd.Usage(); err != nil {
fmt.Println(err)
}
return
}
force, _ := cmd.Flags().GetBool(flagForce)
restart, _ := cmd.Flags().GetBool(flagStart)
err := updateDetectors(args, force, restart)
if err != nil {
fmt.Println(commandUpdate, "command failed")
fmt.Println("Reason:", err)
}
},
}

func init() {
esadCmd.AddCommand(updateCmd)
updateCmd.Flags().BoolP(flagForce, "f", false, "Will stop detector and update it")
updateCmd.Flags().BoolP(flagStart, "r", false, "Will start detector if update is successful")
}

func updateDetectors(fileNames []string, force bool, restart bool) error {
commandHandler, err := getCommandHandler()
if err != nil {
return err
}
for _, name := range fileNames {
err = handler.UpdateAnomalyDetector(commandHandler, name, force, restart)
if err != nil {
return err
}
}
return nil
}
48 changes: 48 additions & 0 deletions cli/internal/controller/ad/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Controller interface {
StopDetectorByName(context.Context, string, bool) error
DeleteDetectorByName(context.Context, string, bool, bool) error
GetDetectorsByName(context.Context, string, bool) ([]*entity.DetectorOutput, error)
UpdateDetector(context.Context, entity.UpdateDetectorUserInput, bool, bool) error
}

type controller struct {
Expand Down Expand Up @@ -520,3 +521,50 @@ func (c controller) GetDetectorsByName(ctx context.Context, pattern string, disp
}
return output, nil
}

//UpdateDetector updates detector based on DetectorID, if force is enabled, it overrides without checking whether
// user downloaded latest version before updating it, if start is true, detector will be started after update
func (c controller) UpdateDetector(ctx context.Context, input entity.UpdateDetectorUserInput, force bool, start bool) error {
if len(input.ID) < 1 {
return fmt.Errorf("detector Id cannot be empty")
}
if !force {
latestDetector, err := c.GetDetector(ctx, input.ID)
if err != nil {
return err
}
if latestDetector.LastUpdatedAt > input.LastUpdatedAt {
return fmt.Errorf(
"new version for detector is available. Please fetch latest version and then merge your changes")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if detector is running? If detector is running and force is false, we should fail immediately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack.
Screen Shot 2020-10-06 at 3 36 47 PM

}
proceed := c.askForConfirmation(
cmapper.StringToStringPtr(
fmt.Sprintf(
"esad will update detector: %s . Do you want to proceed? please type (y)es or (n)o and then press enter:",
input.ID,
),
),
)
if !proceed {
return nil
}
if force { // stop detector implicit since force is true
err := c.StopDetector(ctx, input.ID)
if err != nil {
return err
}
}
payload, err := mapper.MapToUpdateDetector(input)
if err != nil {
return err
}
err = c.gateway.UpdateDetector(ctx, input.ID, payload)
if err != nil {
return err
}
if !start {
return nil
}
return c.StartDetector(ctx, input.ID) // Start Detector if successfully updated it
}
169 changes: 169 additions & 0 deletions cli/internal/controller/ad/ad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,3 +811,172 @@ func TestController_GetDetectorByName(t *testing.T) {
assert.EqualValues(t, *res[0], *detectorOutput)
})
}

func TestController_UpdateDetector(t *testing.T) {
input := entity.UpdateDetectorUserInput{
ID: "m4ccEnIBTXsGi3mvMt9p",
Name: "test-detector",
Description: "Test detector",
TimeField: "timestamp",
Index: []string{"order*"},
Features: []entity.Feature{
{
Name: "total_order",
Enabled: true,
AggregationQuery: []byte(`{"total_order":{"sum":{"field":"value"}}}`),
},
},
Filter: []byte(`{"bool" : {"filter" : [{"exists" : {"field" : "value","boost" : 1.0}}],"adjust_pure_negative" : true,"boost" : 1.0}}`),
Interval: "5m",
Delay: "1m",
LastUpdatedAt: 1589441737319,
SchemaVersion: 0,
}
request := entity.UpdateDetector{
Name: "test-detector",
Description: "Test detector",
TimeField: "timestamp",
Index: []string{"order*"},
Features: []entity.Feature{
{
Name: "total_order",
Enabled: true,
AggregationQuery: []byte(`{"total_order":{"sum":{"field":"value"}}}`),
},
},
Filter: []byte(`{"bool" : {"filter" : [{"exists" : {"field" : "value","boost" : 1.0}}],"adjust_pure_negative" : true,"boost" : 1.0}}`),
Interval: entity.Interval{Period: entity.Period{
Duration: 5,
Unit: "Minutes",
}},
Delay: entity.Interval{Period: entity.Period{
Duration: 1,
Unit: "Minutes",
}},
}
t.Run("update detector without ID", func(t *testing.T) {
invalidInput := input
invalidInput.ID = ""
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockESController := esmockctrl.NewMockController(mockCtrl)
ctx := context.Background()
ctrl := New(os.Stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, invalidInput, true, true)
assert.EqualError(t, err, "detector Id cannot be empty")
})
t.Run("stale detector update failure", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(helperLoadBytes(t, "get_response.json"), nil)
mockESController := esmockctrl.NewMockController(mockCtrl)
staleDetector := input
staleDetector.LastUpdatedAt = input.LastUpdatedAt - 100
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, staleDetector, false, true)
assert.EqualError(t, err, "new version for detector is available. Please fetch latest version and then merge your changes")
})
t.Run("don't update if user says no", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(helperLoadBytes(t, "get_response.json"), nil)
mockESController := esmockctrl.NewMockController(mockCtrl)
var stdin bytes.Buffer
stdin.Write([]byte("no\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, false, true)
assert.NoError(t, err)
})
t.Run("stop detector gateway failed", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().StopDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(nil, errors.New("failed to stop detector"))
mockESController := esmockctrl.NewMockController(mockCtrl)
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, true, true)
assert.EqualError(t, err, "failed to stop detector")
})
t.Run("get detector gateway failed", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(
nil, errors.New("failed to get detector"))
mockESController := esmockctrl.NewMockController(mockCtrl)
ctrl := New(os.Stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, false, false)
assert.EqualError(t, err, "failed to get detector")
})
t.Run("update detector gateway failed", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(helperLoadBytes(t, "get_response.json"), nil)
mockADGateway.EXPECT().UpdateDetector(ctx, "m4ccEnIBTXsGi3mvMt9p", &request).Return(errors.New("failed to update"))
mockESController := esmockctrl.NewMockController(mockCtrl)
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, false, true)
assert.EqualError(t, err, "failed to update")
})
t.Run("start detector gateway failed", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(helperLoadBytes(t, "get_response.json"), nil)
mockADGateway.EXPECT().UpdateDetector(ctx, "m4ccEnIBTXsGi3mvMt9p", &request).Return(nil)
mockADGateway.EXPECT().StartDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(errors.New("failed to start"))
mockESController := esmockctrl.NewMockController(mockCtrl)
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, false, true)
assert.EqualError(t, err, "failed to start")
})
t.Run("force detector update success", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockESController := esmockctrl.NewMockController(mockCtrl)
staleDetector := input
staleDetector.LastUpdatedAt = input.LastUpdatedAt - 100
mockADGateway.EXPECT().StopDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(nil, nil)
mockADGateway.EXPECT().UpdateDetector(ctx, "m4ccEnIBTXsGi3mvMt9p", &request).Return(nil)
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, true, false)
assert.NoError(t, err)
})
t.Run("update detector and start", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ctx := context.Background()
mockADGateway := adgateway.NewMockGateway(mockCtrl)
mockADGateway.EXPECT().GetDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(helperLoadBytes(t, "get_response.json"), nil)
mockADGateway.EXPECT().UpdateDetector(ctx, "m4ccEnIBTXsGi3mvMt9p", &request).Return(nil)
mockADGateway.EXPECT().StartDetector(ctx, "m4ccEnIBTXsGi3mvMt9p").Return(nil)
mockESController := esmockctrl.NewMockController(mockCtrl)
var stdin bytes.Buffer
stdin.Write([]byte("yes\n"))
ctrl := New(&stdin, mockESController, mockADGateway)
err := ctrl.UpdateDetector(ctx, input, false, true)
assert.NoError(t, err)
})
}
14 changes: 14 additions & 0 deletions cli/internal/controller/ad/mocks/mock_ad.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions cli/internal/entity/ad/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,9 @@ type DetectorOutput struct {
LastUpdatedAt uint64 `json:"last_update_time"`
SchemaVersion int32 `json:"schema_version"`
}

//UpdateDetectorUserInput represents user's detector input for update
type UpdateDetectorUserInput DetectorOutput

// UpdateDetector represents detector's settings updated by api
type UpdateDetector CreateDetector
Loading