From c405eed6865efbb22645e0bde6c43aefd302a62d Mon Sep 17 00:00:00 2001 From: Sergei Martynov Date: Wed, 23 Oct 2024 19:40:31 +0200 Subject: [PATCH 1/9] first full V1 implementation --- .env | 2 +- .golangci.yaml | 2 +- docs/readme.md | 5 + docs/v1/incident_creation.drawio | 295 +++++++++++++++++ docs/v1/incident_creation.drawio.png | Bin 0 -> 353723 bytes docs/v1/v1_incident_creation.md | 62 ++++ go.mod | 3 + go.sum | 7 + internal/api/api.go | 36 ++ internal/{app => api/errors}/errors.go | 9 +- internal/{app => api}/handlers.go | 63 ++-- internal/{app => api}/handlers_test.go | 19 +- internal/{app => api}/middleware.go | 45 ++- internal/api/routes.go | 41 +++ internal/api/v1/statuses.go | 25 ++ internal/api/v1/v1.go | 433 +++++++++++++++++++++++++ internal/api/v2/v2.go | 1 + internal/app/app.go | 21 +- internal/app/routes.go | 30 -- internal/db/db.go | 272 ++++++++++++++-- internal/db/models.go | 29 +- openapi.yaml | 4 +- 22 files changed, 1276 insertions(+), 128 deletions(-) create mode 100644 docs/readme.md create mode 100644 docs/v1/incident_creation.drawio create mode 100644 docs/v1/incident_creation.drawio.png create mode 100644 docs/v1/v1_incident_creation.md create mode 100644 internal/api/api.go rename internal/{app => api/errors}/errors.go (78%) rename internal/{app => api}/handlers.go (81%) rename internal/{app => api}/handlers_test.go (89%) rename internal/{app => api}/middleware.go (59%) create mode 100644 internal/api/routes.go create mode 100644 internal/api/v1/statuses.go create mode 100644 internal/api/v1/v1.go create mode 100644 internal/api/v2/v2.go delete mode 100644 internal/app/routes.go diff --git a/.env b/.env index f8e1479..f2f6642 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ # Exampe for environment variables SD_DB=postgresql://pg:pass@localhost:5432/status_dashboard SD_CACHE=internal -#SD_LOG_LEVEL=devel +SD_LOG_LEVEL=devel diff --git a/.golangci.yaml b/.golangci.yaml index a5f93e1..5c17b34 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -165,7 +165,7 @@ linters-settings: allow-no-explanation: [ funlen, gocognit, lll ] # Enable to require an explanation of nonzero length after each nolint directive. # Default: false - require-explanation: true + require-explanation: false # Enable to require nolint directives to mention the specific linter being suppressed. # Default: false require-specific: true diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..e9bba2d --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,5 @@ +# Status dashboard documentation + +## Table of contents + +- [Incident creation for API V1](./v1/v1_incident_creation.md) \ No newline at end of file diff --git a/docs/v1/incident_creation.drawio b/docs/v1/incident_creation.drawio new file mode 100644 index 0000000..bf30e0f --- /dev/null +++ b/docs/v1/incident_creation.drawio @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/v1/incident_creation.drawio.png b/docs/v1/incident_creation.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..7123001040abad441d5b63b5681b2f8e187c1a5b GIT binary patch literal 353723 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 z%0A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%0Arr(uuk8cO-sxM2*HPtnf`!;|7j!A;;g=m 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 literal 0 HcmV?d00001 diff --git a/docs/v1/v1_incident_creation.md b/docs/v1/v1_incident_creation.md new file mode 100644 index 0000000..fdcd2ab --- /dev/null +++ b/docs/v1/v1_incident_creation.md @@ -0,0 +1,62 @@ +# Incident management V1 (short description) + +## Prerequisites + +In this document described the business logic schema for incident management. All actions require authorisation. + +### User experience +Web page `/incidents` + +On the page, user should fill `Incident Title`, `impact`, affected services and start date. All data in the form are required. +After that, in the system will be created a record about this incident. + +### API experience +API endpoint is `/v1/component_status` + +Metric processor can create an incident via API. But the logic a bit different from user experience. +```python +class ComponentStatusArgsSchema(Schema): + impact = fields.Integer(required=True) + name = fields.String(required=True) + text = fields.String(required=False, dump_default="Incident") + attributes = fields.List(fields.Nested(ComponentAttributeSchema)) + +``` +Only `impact` and `name` are required. + +Documentation from source code (can be outdated): + +

Documentation + +Update component status + +Process component status update and open new incident if required: + +- current active maintenance for the component - do nothing +- current active incident for the component - do nothing +- current active incident NOT for the component - add component into + the list of affected components +- no active incidents - create new one +- current active incident for the component and requested + impact > current impact - run handling: + + If a component exists in an incident, but the requested + impact is higher than the current one, then the component + will be moved to another incident if it exists with the + requested impact, otherwise a new incident will be created + and the component will be moved to the new incident. + If there is only one component in an incident, and an + incident with the requested impact does not exist, + then the impact of the incident will be changed to a higher + one, otherwise the component will be moved to an existing + incident with the requested impact, and the current incident + will be closed by the system. + The movement of a component and the closure of an incident + will be reflected in the incident statuses. + +This method requires authorization to be used. +
+ +Based on the source code the schema for API will be: + +![incident creation](incident_creation.drawio.png) diff --git a/go.mod b/go.mod index 20ba43a..fb082d2 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( ) require ( + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/bytedance/sonic v1.12.1 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -34,6 +35,8 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index c7d7b8a..23b4d2f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= @@ -63,6 +65,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -83,6 +89,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..a21dfde --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,36 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "github.com/stackmon/otc-status-dashboard/internal/api/errors" + "github.com/stackmon/otc-status-dashboard/internal/conf" + "github.com/stackmon/otc-status-dashboard/internal/db" +) + +type API struct { + r *gin.Engine + db *db.DB + log *zap.Logger +} + +func New(cfg *conf.Config, log *zap.Logger, database *db.DB) *API { + if cfg.LogLevel != conf.DevelopMode { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.New() + r.Use(Logger(log), gin.Recovery()) + r.Use(ErrorHandle()) + r.Use(CORSMiddleware()) + r.NoRoute(errors.Return404) + + a := &API{r: r, db: database, log: log} + a.initRoutes() + return a +} + +func (a *API) Router() *gin.Engine { + return a.r +} diff --git a/internal/app/errors.go b/internal/api/errors/errors.go similarity index 78% rename from internal/app/errors.go rename to internal/api/errors/errors.go index 1ff92d1..4c02d01 100644 --- a/internal/app/errors.go +++ b/internal/api/errors/errors.go @@ -1,4 +1,4 @@ -package app +package errors import ( "errors" @@ -27,20 +27,21 @@ var ErrIncidentDSNotExist = errors.New("incident does not exist") var ErrComponentDSNotExist = errors.New("component does not exist") var ErrComponentInvalidFormat = errors.New("component invalid format") +var ErrComponentRegionAttrMissing = errors.New("component attribute region missing") func Return404(c *gin.Context) { c.JSON(http.StatusNotFound, ReturnError(ErrPageNotFound)) } -func raiseInternalErr(c *gin.Context, err error) { +func RaiseInternalErr(c *gin.Context, err error) { intErr := fmt.Errorf("%w: %w", ErrInternalError, err) _ = c.AbortWithError(http.StatusInternalServerError, ReturnError(intErr)) } -func raiseBadRequestErr(c *gin.Context, err error) { +func RaiseBadRequestErr(c *gin.Context, err error) { _ = c.AbortWithError(http.StatusBadRequest, ReturnError(err)) } -func raiseStatusNotFoundErr(c *gin.Context, err error) { +func RaiseStatusNotFoundErr(c *gin.Context, err error) { _ = c.AbortWithError(http.StatusNotFound, ReturnError(err)) } diff --git a/internal/app/handlers.go b/internal/api/handlers.go similarity index 81% rename from internal/app/handlers.go rename to internal/api/handlers.go index 03dd1c4..4836c6b 100644 --- a/internal/app/handlers.go +++ b/internal/api/handlers.go @@ -1,4 +1,4 @@ -package app +package api import ( "errors" @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" + apiErrors "github.com/stackmon/otc-status-dashboard/internal/api/errors" "github.com/stackmon/otc-status-dashboard/internal/db" ) @@ -36,10 +37,10 @@ type Incident struct { IncidentData } -func (a *App) GetIncidentsHandler(c *gin.Context) { - r, err := a.DB.GetIncidents() +func (a *API) GetIncidentsHandler(c *gin.Context) { + r, err := a.db.GetIncidents() if err != nil { - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } @@ -58,7 +59,7 @@ func (a *App) GetIncidentsHandler(c *gin.Context) { Components: components, StartDate: *inc.StartDate, EndDate: inc.EndDate, - System: inc.System, + System: &inc.System, Updates: inc.Statuses, }, } @@ -67,20 +68,20 @@ func (a *App) GetIncidentsHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"data": incidents}) } -func (a *App) GetIncidentHandler(c *gin.Context) { +func (a *API) GetIncidentHandler(c *gin.Context) { var incID IncidentID if err := c.ShouldBindUri(&incID); err != nil { - raiseBadRequestErr(c, err) + apiErrors.RaiseBadRequestErr(c, err) return } - r, err := a.DB.GetIncident(incID.ID) + r, err := a.db.GetIncident(incID.ID) if err != nil { if errors.Is(err, db.ErrDBIncidentDSNotExist) { - raiseStatusNotFoundErr(c, ErrIncidentDSNotExist) + apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrIncidentDSNotExist) return } - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } @@ -95,17 +96,17 @@ func (a *App) GetIncidentHandler(c *gin.Context) { Components: components, StartDate: *r.StartDate, EndDate: r.EndDate, - System: r.System, + System: &r.System, Updates: r.Statuses, } c.JSON(http.StatusOK, &Incident{incID, incData}) } -func (a *App) PostIncidentHandler(c *gin.Context) { +func (a *API) PostIncidentHandler(c *gin.Context) { var incData IncidentData if err := c.ShouldBindBodyWithJSON(&incData); err != nil { - raiseBadRequestErr(c, err) + apiErrors.RaiseBadRequestErr(c, err) return } @@ -119,13 +120,13 @@ func (a *App) PostIncidentHandler(c *gin.Context) { StartDate: &incData.StartDate, EndDate: incData.EndDate, Impact: incData.Impact, - System: incData.System, + System: *incData.System, Components: components, } - incidentID, err := a.DB.SaveIncident(&dbInc) + incidentID, err := a.db.SaveIncident(&dbInc) if err != nil { - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } @@ -152,16 +153,16 @@ type PatchIncidentData struct { Update *db.IncidentStatus `json:"update,omitempty"` } -func (a *App) PatchIncidentHandler(c *gin.Context) { +func (a *API) PatchIncidentHandler(c *gin.Context) { var incID IncidentID if err := c.ShouldBindUri(&incID); err != nil { - raiseBadRequestErr(c, err) + apiErrors.RaiseBadRequestErr(c, err) return } var incData PatchIncidentData if err := c.ShouldBindBodyWithJSON(&incData); err != nil { - raiseBadRequestErr(c, err) + apiErrors.RaiseBadRequestErr(c, err) return } @@ -184,14 +185,14 @@ func (a *App) PatchIncidentHandler(c *gin.Context) { StartDate: incData.StartDate, EndDate: incData.EndDate, Impact: incData.Impact, - System: incData.System, + System: *incData.System, Components: components, Statuses: statuses, } - err := a.DB.ModifyIncident(&dbInc) + err := a.db.ModifyIncident(&dbInc) if err != nil { - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } @@ -213,30 +214,30 @@ type ComponentAttribute struct { Value string `json:"value"` } -func (a *App) GetComponentsStatusHandler(c *gin.Context) { - r, err := a.DB.GetComponentsWithValues() +func (a *API) GetComponentsStatusHandler(c *gin.Context) { + r, err := a.db.GetComponentsWithValues() if err != nil { - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } c.JSON(http.StatusOK, r) } -func (a *App) GetComponentHandler(c *gin.Context) { +func (a *API) GetComponentHandler(c *gin.Context) { var compID ComponentID if err := c.ShouldBindUri(&compID); err != nil { - raiseBadRequestErr(c, ErrComponentInvalidFormat) + apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentInvalidFormat) return } - r, err := a.DB.GetComponent(compID.ID) + r, err := a.db.GetComponent(compID.ID) if err != nil { if errors.Is(err, db.ErrDBComponentDSNotExist) { - raiseStatusNotFoundErr(c, ErrComponentDSNotExist) + apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrComponentDSNotExist) return } - raiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) return } @@ -269,6 +270,6 @@ func (a *App) GetComponentHandler(c *gin.Context) { // will be closed by the system. // The movement of a component and the closure of an incident // will be reflected in the incident statuses. -func (a *App) PostComponentStatusHandler(c *gin.Context) { +func (a *API) PostComponentStatusHandler(c *gin.Context) { c.JSON(http.StatusOK, map[string]string{"status": "in development"}) } diff --git a/internal/app/handlers_test.go b/internal/api/handlers_test.go similarity index 89% rename from internal/app/handlers_test.go rename to internal/api/handlers_test.go index 1811e7e..a3c4274 100644 --- a/internal/app/handlers_test.go +++ b/internal/api/handlers_test.go @@ -1,4 +1,4 @@ -package app +package api import ( "database/sql/driver" @@ -13,10 +13,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stackmon/otc-status-dashboard/internal/api/errors" "github.com/stackmon/otc-status-dashboard/internal/db" ) -var testApp *App +var testAPI *API var mock sqlmock.Sqlmock func TestGetIncidentsHandler(t *testing.T) { @@ -33,7 +34,7 @@ func TestGetIncidentsHandler(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/v1/incidents", nil) - testApp.router.ServeHTTP(w, req) + testAPI.r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) @@ -44,7 +45,7 @@ func TestReturn404Handler(t *testing.T) { initTests(t) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/anyendpoint", nil) - testApp.router.ServeHTTP(w, req) + testAPI.r.ServeHTTP(w, req) assert.Equal(t, 404, w.Code) assert.Equal(t, `{"errMsg":"page not found"}`, w.Body.String()) @@ -90,19 +91,19 @@ func prepareDB(t *testing.T, testTime time.Time) { func initTests(t *testing.T) { t.Helper() - if testApp != nil && mock != nil { - t.Log("testApp and mock are initialized") + if testAPI != nil && mock != nil { + t.Log("testAPI and mock are initialized") } t.Log("start initialisation") r := gin.Default() r.Use(ErrorHandle()) - r.NoRoute(Return404) + r.NoRoute(errors.Return404) d, m, err := db.NewWithMock() require.NoError(t, err) - testApp = &App{router: r, Log: nil, conf: nil, DB: d, srv: nil} - testApp.InitRoutes() + testAPI = &API{r: r, db: d} + testAPI.initRoutes() mock = m } diff --git a/internal/app/middleware.go b/internal/api/middleware.go similarity index 59% rename from internal/app/middleware.go rename to internal/api/middleware.go index ee8b6e5..4036f6d 100644 --- a/internal/app/middleware.go +++ b/internal/api/middleware.go @@ -1,4 +1,4 @@ -package app +package api import ( "errors" @@ -9,9 +9,11 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + errors2 "github.com/stackmon/otc-status-dashboard/internal/api/errors" ) -func (a *App) ValidateComponentsMW() gin.HandlerFunc { +func (a *API) ValidateComponentsMW() gin.HandlerFunc { return func(c *gin.Context) { type Components struct { Components []int `json:"components"` @@ -20,7 +22,7 @@ func (a *App) ValidateComponentsMW() gin.HandlerFunc { var components Components if err := c.ShouldBindBodyWithJSON(&components); err != nil { - raiseBadRequestErr(c, fmt.Errorf("%w: %w", ErrComponentInvalidFormat, err)) + errors2.RaiseBadRequestErr(c, fmt.Errorf("%w: %w", errors2.ErrComponentInvalidFormat, err)) return } @@ -28,25 +30,25 @@ func (a *App) ValidateComponentsMW() gin.HandlerFunc { // We should check, that all components are presented in our db. err := a.IsPresentComponent(components.Components) if err != nil { - if errors.Is(err, ErrComponentDSNotExist) { - raiseBadRequestErr(c, err) + if errors.Is(err, errors2.ErrComponentDSNotExist) { + errors2.RaiseBadRequestErr(c, err) } else { - raiseInternalErr(c, err) + errors2.RaiseInternalErr(c, err) } } c.Next() } } -func (a *App) IsPresentComponent(components []int) error { - dbComps, err := a.DB.GetComponentsAsMap() +func (a *API) IsPresentComponent(components []int) error { + dbComps, err := a.db.GetComponentsAsMap() if err != nil { return err } for _, comp := range components { if _, ok := dbComps[comp]; !ok { - return ErrComponentDSNotExist + return errors2.ErrComponentDSNotExist } } @@ -65,10 +67,10 @@ func ErrorHandle() gin.HandlerFunc { var err error err = c.Errors.Last() if status >= http.StatusInternalServerError { - err = ErrInternalError + err = errors2.ErrInternalError } - c.JSON(-1, ReturnError(err)) + c.JSON(-1, errors2.ReturnError(err)) } } @@ -98,7 +100,7 @@ func Logger(log *zap.Logger) gin.HandlerFunc { switch { case c.Writer.Status() >= http.StatusInternalServerError: - msg := fmt.Sprintf("panic was recovered, %s", ErrInternalError) + msg := fmt.Sprintf("panic was recovered, %s", errors2.ErrInternalError) if c.Errors.Last() != nil { msg = c.Errors.Last().Error() } @@ -112,3 +114,22 @@ func Logger(log *zap.Logger) gin.HandlerFunc { } } } + +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set( + "Access-Control-Allow-Headers", + "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, "+ + "Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + + if c.Request.Method == http.MethodOptions { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} diff --git a/internal/api/routes.go b/internal/api/routes.go new file mode 100644 index 0000000..e0ae24a --- /dev/null +++ b/internal/api/routes.go @@ -0,0 +1,41 @@ +package api + +import v1 "github.com/stackmon/otc-status-dashboard/internal/api/v1" + +const ( + v1Group = "v1" + v2Group = "v2" +) + +func (a *API) initRoutes() { + v1Api := a.r.Group(v1Group) + { + v1Api.GET("component_status", v1.GetComponentsStatusHandler(a.db, a.log)) + v1Api.POST("component_status", v1.PostComponentStatusHandler(a.db, a.log)) + + v1Api.GET("incidents", v1.GetIncidentsHandler(a.db, a.log)) + } + //nolint:gocritic + // setup v2 group routing + //v2 := a.router.Group(v2Group) + //{ + // v2.GET("components", a.GetComponentsStatusHandler) + // v2.GET("components/:id", a.GetComponentHandler) + // v2.GET("component_status", a.GetComponentsStatusHandler) + // v2.POST("component_status", a.PostComponentStatusHandler) + // + // v2.GET("incidents", a.GetIncidentsHandler) + // v2.POST("incidents", a.ValidateComponentsMW(), a.PostIncidentHandler) + // v2.GET("incidents/:id", a.GetIncidentHandler) + // v2.PATCH("incidents/:id", a.ValidateComponentsMW(), a.PatchIncidentHandler) + // + // v2.GET("rss") + // v2.GET("history") + // v2.GET("availability") + // v2.GET("/separate//") - > investigate it!!! + // + // v2.GET("/login/:name") + // v2.GET("/auth/:name") + // v2.GET("/logout") + //} +} diff --git a/internal/api/v1/statuses.go b/internal/api/v1/statuses.go new file mode 100644 index 0000000..385cf3a --- /dev/null +++ b/internal/api/v1/statuses.go @@ -0,0 +1,25 @@ +package v1 + +// TODO: move these statuses to consts and use it +// +//nolint:unused,gochecknoglobals +var stasuses = ` +MAINTENANCE_STATUSES = { +"in progress": "Maintenance is in progress", +"modified": "Maintenance time window has been modified", +"completed": "Maintenance is successfully completed", +} + +INCIDENT_STATUSES = { +"analyzing": "Analyzing incident (problem not known yet)", +"fixing": "Fixing incident (problem identified, working on fix)", +"impact changed": "Impact changed (incident impact has been changed)", +"observing": "Observing fix (fix deployed, watching recovery)", +"resolved": "Incident Resolved (service is fully available. Done)", +} + +INCIDENT_ACTIONS = { +"reopened": "Incident reopened (resolved incident has ben reopened)", +"changed": "Incident changed: (end date has been changed)", +} +` diff --git a/internal/api/v1/v1.go b/internal/api/v1/v1.go new file mode 100644 index 0000000..be3afcf --- /dev/null +++ b/internal/api/v1/v1.go @@ -0,0 +1,433 @@ +package v1 + +import ( + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + apiErrors "github.com/stackmon/otc-status-dashboard/internal/api/errors" + "github.com/stackmon/otc-status-dashboard/internal/db" +) + +const ( + timeLayout = "2006-01-02 15:04" +) + +type IncidentID struct { + ID int `json:"id" uri:"id" binding:"required,gte=0"` +} + +type IncidentData struct { + Text string `json:"text" binding:"required"` + // INCIDENT_IMPACTS = { + // 0: Impact(0, "maintenance", "Scheduled maintenance"), + // 1: Impact(1, "minor", "Minor incident (i.e. performance impact)"), + // 2: Impact(2, "major", "Major incident"), + // 3: Impact(3, "outage", "Service outage"), + // } + Impact *int `json:"impact" binding:"required,gte=0,lte=3"` + // datetime format is "2006-01-01 12:00" + StartDate SD2Time `json:"start_date" binding:"required"` + EndDate *SD2Time `json:"end_date,omitempty"` + Updates []*IncidentStatus `json:"updates,omitempty"` +} + +// IncidentStatus is a db table representation. +type IncidentStatus struct { + Status string `json:"status"` + Text string `json:"text"` + Timestamp SD2Time `json:"timestamp"` +} + +type Incident struct { + IncidentID + IncidentData +} + +type SD2Time time.Time + +func (s *SD2Time) MarshalJSON() ([]byte, error) { + sTime := time.Time(*s) + str := sTime.Format(timeLayout) + + return json.Marshal(str) +} + +func (s *SD2Time) UnmarshalJSON(data []byte) error { + t, err := time.Parse(timeLayout, string(data)) + if err != nil { + return err + } + *s = SD2Time(t) + return nil +} + +func GetIncidentsHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve incidents") + r, err := db.GetIncidents() + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + incidents := make([]*Incident, len(r)) + for i, inc := range r { + updates := make([]*IncidentStatus, len(inc.Statuses)) + for index, status := range inc.Statuses { + updates[index] = &IncidentStatus{ + Status: status.Status, + Text: status.Text, + Timestamp: SD2Time(status.Timestamp), + } + } + + endDate := SD2Time(*inc.EndDate) + incidents[i] = &Incident{ + IncidentID: IncidentID{int(inc.ID)}, + IncidentData: IncidentData{ + Text: *inc.Text, + Impact: inc.Impact, + StartDate: SD2Time(*inc.StartDate), + EndDate: &endDate, + Updates: updates, + }, + } + } + + c.JSON(http.StatusOK, incidents) + } +} + +type Component struct { + ComponentID + Attrs []*ComponentAttribute `json:"attributes"` + Name string `json:"name"` + Incidents []*Incident `json:"incidents"` +} + +type ComponentID struct { + ID int `json:"id" uri:"id" binding:"required,gte=0"` +} + +type ComponentAttribute struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve components with incidents") + r, err := db.GetComponentsWithIncidents() + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + // We can't change this logic, because of date representation. + // Will be changed in the V2. + components := make([]*Component, len(r)) + for index, component := range r { + attrs := make([]*ComponentAttribute, len(component.Attrs)) + for i, attr := range component.Attrs { + attrs[i] = &ComponentAttribute{ + Name: attr.Name, + Value: attr.Value, + } + } + + incidents := make([]*Incident, len(component.Incidents)) + for i, inc := range component.Incidents { + var endDate SD2Time + if inc.EndDate != nil { + endDate = SD2Time(*inc.EndDate) + } + + newInc := &Incident{ + IncidentID: IncidentID{int(inc.ID)}, + IncidentData: IncidentData{ + Text: *inc.Text, + Impact: inc.Impact, + StartDate: SD2Time(*inc.StartDate), + EndDate: &endDate, + Updates: nil, + }, + } + + updates := make([]*IncidentStatus, len(inc.Statuses)) + for ind, status := range inc.Statuses { + updates[ind] = &IncidentStatus{ + Status: status.Status, + Text: status.Text, + Timestamp: SD2Time(status.Timestamp), + } + } + + newInc.Updates = updates + + incidents[i] = newInc + } + + components[index] = &Component{ + ComponentID: ComponentID{int(component.ID)}, + Attrs: attrs, + Name: component.Name, + Incidents: incidents, + } + } + + c.JSON(http.StatusOK, components) + } +} + +type ComponentStatusPost struct { + Name string `json:"name" binding:"required"` + Impact int `json:"impact" binding:"required,gte=1,lte=3"` + Text string `json:"text"` + Attributes []*ComponentAttribute `json:"attributes" binding:"required"` +} + +// PostComponentStatusHandler creates a new component. +// TODO: copy-paste from the legacy, it's implemented, but only for API. We should discuss about this functionality. +// +// Process component status update and open new incident if required: +// +// - current active maintenance for the component - do nothing +// - current active incident for the component - do nothing +// - current active incident NOT for the component - add component into +// the list of affected components +// - no active incidents - create new one +// - current active incident for the component and requested +// impact > current impact - run handling: +// +// If a component exists in an incident, but the requested +// impact is higher than the current one, then the component +// will be moved to another incident if it exists with the +// requested impact, otherwise a new incident will be created +// and the component will be moved to the new incident. +// If there is only one component in an incident, and an +// incident with the requested impact does not exist, +// then the impact of the incident will be changed to a higher +// one, otherwise the component will be moved to an existing +// incident with the requested impact, and the current incident +// will be closed by the system. +// The movement of a component and the closure of an incident +// will be reflected in the incident statuses. +func PostComponentStatusHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { //nolint:gocognit + return func(c *gin.Context) { + var inComponent ComponentStatusPost + attrs, err := extractComponentAttr(c, &inComponent) + if err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + log := logger.With(zap.Any("component", inComponent)) + + log.Info("get component from name and attributes") + storedComponent, err := dbInst.GetComponentFromNameAttrs(inComponent.Name, attrs) + if err != nil { + if errors.Is(err, db.ErrDBComponentDSNotExist) { + apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentDSNotExist) + return + } + apiErrors.RaiseInternalErr(c, err) + return + } + + log.Info("get opened incidents") + openedIncidents, err := dbInst.GetIncidents(&db.IncidentsParams{IsOpened: true}) + if err != nil && !errors.Is(err, db.ErrDBIncidentDSNotExist) { + apiErrors.RaiseInternalErr(c, err) + return + } + + if err != nil && errors.Is(err, db.ErrDBIncidentDSNotExist) { + log.Info("there are no opened incidents") + inc, errCreation := createIncident(dbInst, log, storedComponent, &inComponent) + if errCreation != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + c.JSON(http.StatusCreated, inc) + return + } + + log.Info("find opened incident with the component") + // the strange logic, because we will get the first incident with component, but we can have another one + incident := getIncidentWithComponent(storedComponent.ID, openedIncidents) + if incident == nil { + log.Info("there are no incidents with given component, find an incident with incoming impact") + incByImpact := findIncidentByImpact(inComponent.Impact, openedIncidents) + if incByImpact != nil { + // add component to the founded incident + incByImpact.Components = append(incByImpact.Components, *storedComponent) + err = dbInst.ModifyIncident(incByImpact) + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + c.JSON(http.StatusCreated, toAPIIncident(incByImpact)) + return + } + + log.Info("there are no incidents with given component and impact, create an incident") + inc, errCreation := createIncident(dbInst, log, storedComponent, &inComponent) + if errCreation != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + c.JSON(http.StatusCreated, inc) + return + } + + if *incident.Impact == 0 { + log.Info("the incident with component in the maintenance status, skip it") + // the status code is the legacy + c.JSON(http.StatusCreated, toAPIIncident(incident)) + return + } + + if *incident.Impact >= inComponent.Impact { + log.Info("the incident impact is higher than incoming, skip it") + c.JSON(http.StatusConflict, returnConflictResponse(storedComponent, incident)) + return + } + + storedIncident, err := moveIncidentToHigherImpact( + dbInst, log, storedComponent, + incident, openedIncidents, + inComponent.Impact, inComponent.Text) + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusCreated, toAPIIncident(storedIncident)) + } +} + +func extractComponentAttr(c *gin.Context, in *ComponentStatusPost) (*db.ComponentAttr, error) { + if err := c.ShouldBindBodyWithJSON(in); err != nil { + return nil, apiErrors.ErrComponentInvalidFormat + } + + var dbAttr *db.ComponentAttr + for _, attr := range in.Attributes { + if attr.Name == "region" { + dbAttr = &db.ComponentAttr{ + Name: attr.Name, + Value: attr.Value, + } + } + } + + if dbAttr == nil { + return nil, apiErrors.ErrComponentRegionAttrMissing + } + + return dbAttr, nil +} + +func returnConflictResponse(comp *db.Component, inc *db.Incident) map[string]any { + return map[string]any{ + "message": "Incident with this the component already exists", + "targetComponent": comp, + "existingIncidentId": inc.ID, + "existingIncidentTitle": inc.Text, + "details": "Check your request parameters", + } +} + +func createIncident( + dbInst *db.DB, log *zap.Logger, storedComponent *db.Component, inComponent *ComponentStatusPost, +) (*Incident, error) { + log.Info("start to create an incident") + startDate := time.Now() + comps := []db.Component{*storedComponent} + inc := &db.Incident{ + Text: &inComponent.Text, + StartDate: &startDate, + EndDate: nil, + Impact: &inComponent.Impact, + Statuses: nil, + Components: comps, + } + id, err := dbInst.SaveIncident(inc) + if err != nil { + return nil, err + } + inc.ID = id + return toAPIIncident(inc), nil +} + +func getIncidentWithComponent(componentID uint, incidents []*db.Incident) *db.Incident { + for i, incident := range incidents { + for _, component := range incident.Components { + if componentID == component.ID { + return incidents[i] + } + } + } + + return nil +} + +func toAPIIncident(incident *db.Incident) *Incident { + updates := make([]*IncidentStatus, len(incident.Statuses)) + for i, s := range incident.Statuses { + updates[i] = &IncidentStatus{ + Status: s.Status, + Text: s.Text, + Timestamp: SD2Time(s.Timestamp), + } + } + return &Incident{ + IncidentID: IncidentID{ID: int(incident.ID)}, + IncidentData: IncidentData{ + Text: *incident.Text, + Impact: incident.Impact, + StartDate: SD2Time(*incident.StartDate), + Updates: updates, + }, + } +} + +func moveIncidentToHigherImpact( + dbInst *db.DB, log *zap.Logger, + storedComponent *db.Component, incident *db.Incident, incidents []*db.Incident, + impact int, text string, +) (*db.Incident, error) { + incWithHighImpact := findIncidentByImpact(impact, incidents) + if incWithHighImpact == nil { + if len(incident.Components) > 1 { + log.Info("no active incidents with requested impact, opening the new one") + return dbInst.ExtractComponentToNewIncident(storedComponent, incident, impact, text) + } + log.Info("only one component in the incident, increase impact") + return dbInst.IncreaseIncidentImpact(incident, impact) + } + + if len(incident.Components) == 1 { + log.Info("move component to the incident with the found impact, close current incident") + return dbInst.AddComponentToNewIncidentAndCloseOld(storedComponent, incident, incWithHighImpact) + } + + // In that case we have the existed incident with target impact (greater where component is presented) + // And count of components is more than one. We should move component from old to new. + log.Info("move component to the incident with the higher impact") + return dbInst.MoveComponentFromOldToAnotherIncident(storedComponent, incident, incWithHighImpact) +} + +func findIncidentByImpact(impact int, incidents []*db.Incident) *db.Incident { + for _, incident := range incidents { + if *incident.Impact == impact { + return incident + } + } + return nil +} diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go new file mode 100644 index 0000000..5ec3cc8 --- /dev/null +++ b/internal/api/v2/v2.go @@ -0,0 +1 @@ +package v2 diff --git a/internal/app/app.go b/internal/app/app.go index 5bd753d..68d93c7 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,9 +6,9 @@ import ( "net/http" "time" - "github.com/gin-gonic/gin" "go.uber.org/zap" + "github.com/stackmon/otc-status-dashboard/internal/api" "github.com/stackmon/otc-status-dashboard/internal/conf" "github.com/stackmon/otc-status-dashboard/internal/db" ) @@ -21,7 +21,7 @@ type App struct { // Configuration conf *conf.Config // Router - router *gin.Engine + api *api.API // zap logger Log *zap.Logger // db connection @@ -31,28 +31,21 @@ type App struct { } func New(c *conf.Config, log *zap.Logger) (*App, error) { - if c.LogLevel != conf.DevelopMode { - gin.SetMode(gin.ReleaseMode) - } - - d, err := db.New(c) + dbNew, err := db.New(c) if err != nil { return nil, err } - r := gin.New() - r.Use(Logger(log), gin.Recovery()) - r.Use(ErrorHandle()) - r.NoRoute(Return404) + apiNew := api.New(c, log, dbNew) s := &http.Server{ Addr: fmt.Sprintf(":%s", c.Port), - Handler: r, + Handler: apiNew.Router(), ReadHeaderTimeout: readHeaderTimeout, } - a := &App{router: r, Log: log, conf: c, DB: d, srv: s} - a.InitRoutes() + a := &App{api: apiNew, Log: log, conf: c, DB: dbNew, srv: s} + return a, nil } diff --git a/internal/app/routes.go b/internal/app/routes.go deleted file mode 100644 index 6cc5929..0000000 --- a/internal/app/routes.go +++ /dev/null @@ -1,30 +0,0 @@ -package app - -const ( - v1Group = "v1" -) - -func (a *App) InitRoutes() { - // setup v1 group routing - v1 := a.router.Group(v1Group) - { - v1.GET("components", a.GetComponentsStatusHandler) - v1.GET("components/:id", a.GetComponentHandler) - v1.GET("component_status", a.GetComponentsStatusHandler) - v1.POST("component_status", a.PostComponentStatusHandler) - - v1.GET("incidents", a.GetIncidentsHandler) - v1.POST("incidents", a.ValidateComponentsMW(), a.PostIncidentHandler) - v1.GET("incidents/:id", a.GetIncidentHandler) - v1.PATCH("incidents/:id", a.ValidateComponentsMW(), a.PatchIncidentHandler) - - v1.GET("rss") - v1.GET("history") - v1.GET("availability") - v1.GET("/separate//") - - v1.GET("/login/:name") - v1.GET("/auth/:name") - v1.GET("/logout") - } -} diff --git a/internal/db/db.go b/internal/db/db.go index 1da0e0a..4842318 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -2,6 +2,9 @@ package db import ( "errors" + "fmt" + "slices" + "time" "go.uber.org/zap" "gorm.io/driver/postgres" @@ -35,15 +38,30 @@ func New(c *conf.Config) (*DB, error) { return &DB{g: g}, nil } -func (db *DB) GetIncidents() ([]Incident, error) { - var incidents []Incident - r := db.g.Model(&Incident{}). +type IncidentsParams struct { + IsOpened bool +} + +func (db *DB) GetIncidents(params ...*IncidentsParams) ([]*Incident, error) { + var incidents []*Incident + var param *IncidentsParams + if len(params) != 0 && params[0] != nil { + param = params[0] + } + + r := db.g.Debug().Model(&Incident{}). Preload("Statuses"). - Preload( - "Components", func(db *gorm.DB) *gorm.DB { - return db.Select("ID") - }). - Find(&incidents) + Preload("Components", func(db *gorm.DB) *gorm.DB { return db.Select("ID") }) + + if param.IsOpened { + r.Where("end_date is NULL").Find(&incidents) + if r.Error != nil { + return nil, r.Error + } + return incidents, nil + } + + r.Find(&incidents) if r.Error != nil { return nil, r.Error @@ -57,10 +75,9 @@ func (db *DB) GetIncident(id int) (*Incident, error) { r := db.g.Model(&Incident{}). Where(inc). Preload("Statuses"). - Preload( - "Components", func(db *gorm.DB) *gorm.DB { - return db.Select("ID") - }). + Preload("Components", func(db *gorm.DB) *gorm.DB { + return db.Select("ID") + }). First(&inc) if r.Error != nil { @@ -83,25 +100,41 @@ func (db *DB) SaveIncident(inc *Incident) (uint, error) { return inc.ID, nil } +// TODO: check this function for patching incident func (db *DB) ModifyIncident(inc *Incident) error { - var components []Component + r := db.g.Updates(inc) - if inc.Components != nil { - components = inc.Components - inc.Components = nil + if r.Error != nil { + return r.Error } - r := db.g.Updates(inc) + return nil +} - if components != nil { //nolint:revive,staticcheck,nolintlint - // TODO: update components here +func (db *DB) GetOpenedIncidentsWithComponent(name string, attrs []ComponentAttr) (*Incident, error) { + comp := &Component{Name: name, Attrs: attrs} + r := db.g.Model(&Component{}).Preload("Attrs").Find(comp) + if r.Error != nil { + if errors.Is(r.Error, gorm.ErrRecordNotFound) { + return nil, ErrDBComponentDSNotExist + } + return nil, r.Error } + var incident Incident + r = db.g.Model(&Incident{}). + Preload("Statuses"). + Preload("Components", func(db *gorm.DB) *gorm.DB { + return db.Select("ID") + }). + // Where("component_id = ?", comp.ID). + First(&incident) + if r.Error != nil { - return r.Error + return nil, r.Error } - return nil + return &incident, nil } func (db *DB) GetComponent(id int) (*Component, error) { @@ -144,3 +177,200 @@ func (db *DB) GetComponentsWithValues() ([]Component, error) { return components, nil } + +func (db *DB) GetComponentsWithIncidents() ([]Component, error) { + var components []Component + r := db.g.Model(&Component{}).Preload("Attrs").Preload("Incidents").Preload("Incidents.Statuses").Find(&components) + + if r.Error != nil { + return nil, r.Error + } + + return components, nil +} + +func (db *DB) GetComponentFromNameAttrs(name string, attrs *ComponentAttr) (*Component, error) { + comp := Component{} + //nolint:lll + // You can reproduce this raw request + // select * from component join component_attribute ca on component.id=ca.component_id + // where component.id = + // (select component.id from component join component_attribute ca on component.id = ca.component_id and ca.value='EU-DE' and component.name='Cloud Container Engine'); + r := db.g.Model(&Component{}). + Where(&Component{Name: name}). + Preload("Attrs", func(db *gorm.DB) *gorm.DB { + return db.Where("name=?", attrs.Name).Where("value=?", attrs.Value) + }). + Preload("Attrs").Find(&comp) + + if r.Error != nil { + return nil, r.Error + } + + return &comp, nil +} + +const statusSYSTEM = "SYSTEM" + +func (db *DB) MoveComponentFromOldToAnotherIncident(comp *Component, incOld, incNew *Incident) (*Incident, error) { + timeNow := time.Now() + + incNew.Components = append(incNew.Components, *comp) + text := fmt.Sprintf("%s moved from %s", comp.PrintAttrs(), incOld.Link()) + incNew.Statuses = append(incNew.Statuses, IncidentStatus{ + IncidentID: incNew.ID, + Status: statusSYSTEM, + Text: text, + Timestamp: timeNow, + }) + + var indexToRemove int + for i, c := range incOld.Components { + if c.ID == comp.ID { + indexToRemove = i + } + } + incOld.Components = slices.Delete(incOld.Components, indexToRemove, indexToRemove+1) + text = fmt.Sprintf("%s moved to %s", comp.PrintAttrs(), incNew.Link()) + incOld.Statuses = append(incOld.Statuses, IncidentStatus{ + IncidentID: incOld.ID, + Status: statusSYSTEM, + Text: text, + Timestamp: timeNow, + }) + + err := db.g.Transaction(func(tx *gorm.DB) error { + if r := tx.Save(incNew); r.Error != nil { + return r.Error + } + if r := tx.Save(incOld); r.Error != nil { + return r.Error + } + + return nil + }) + + if err != nil { + return nil, err + } + + return incNew, nil +} + +func (db *DB) AddComponentToNewIncidentAndCloseOld(comp *Component, incToClose, inc *Incident) (*Incident, error) { + if len(inc.Components) == 0 { + var comps []Component + inc.Components = comps + } + inc.Components = append(inc.Components, *comp) + + timeNow := time.Now() + + text := fmt.Sprintf("%s moved from %s", comp.PrintAttrs(), inc.Link()) + inc.Statuses = append(inc.Statuses, IncidentStatus{ + IncidentID: inc.ID, + Status: statusSYSTEM, + Text: text, + Timestamp: timeNow, + }) + + closedText := fmt.Sprintf("%s moved to %s, Incident closed by system", comp.PrintAttrs(), inc.Link()) + incToClose.Statuses = append(incToClose.Statuses, IncidentStatus{ + IncidentID: incToClose.ID, + Status: statusSYSTEM, + Text: closedText, + Timestamp: timeNow, + }) + incToClose.EndDate = &timeNow + + err := db.g.Transaction(func(tx *gorm.DB) error { + if r := tx.Save(inc); r.Error != nil { + return r.Error + } + if r := tx.Save(incToClose); r.Error != nil { + return r.Error + } + + return nil + }) + + if err != nil { + return nil, err + } + + return inc, nil +} + +func (db *DB) ExtractComponentToNewIncident( + comp *Component, oldIncident *Incident, impact int, text string, +) (*Incident, error) { + timeNow := time.Now() + + inc := &Incident{ + Text: &text, + StartDate: &timeNow, + EndDate: nil, + Impact: &impact, + Statuses: nil, + System: false, + Components: []Component{*comp}, + } + + id, err := db.SaveIncident(inc) + if err != nil { + return nil, err + } + + incText := fmt.Sprintf("%s moved from %s", comp.PrintAttrs(), oldIncident.Link()) + inc.Statuses = append(inc.Statuses, IncidentStatus{ + IncidentID: id, + Status: statusSYSTEM, + Text: incText, + Timestamp: timeNow, + }) + + err = db.ModifyIncident(inc) + if err != nil { + return nil, err + } + + var indexToRemove int + for i, c := range oldIncident.Components { + if c.ID == comp.ID { + indexToRemove = i + } + } + oldIncident.Components = slices.Delete(oldIncident.Components, indexToRemove, indexToRemove+1) + incText = fmt.Sprintf("%s moved to %s", comp.PrintAttrs(), inc.Link()) + oldIncident.Statuses = append(oldIncident.Statuses, IncidentStatus{ + IncidentID: inc.ID, + Status: statusSYSTEM, + Text: incText, + Timestamp: timeNow, + }) + + err = db.ModifyIncident(oldIncident) + if err != nil { + return nil, err + } + + return inc, nil +} + +func (db *DB) IncreaseIncidentImpact(inc *Incident, impact int) (*Incident, error) { + timeNow := time.Now() + text := fmt.Sprintf("impact changed from %d to %d", inc.Impact, impact) + inc.Statuses = append(inc.Statuses, IncidentStatus{ + IncidentID: inc.ID, + Status: statusSYSTEM, + Text: text, + Timestamp: timeNow, + }) + inc.Impact = &impact + + if r := db.g.Save(inc); r.Error != nil { + return nil, r.Error + } + + return inc, nil +} diff --git a/internal/db/models.go b/internal/db/models.go index b9d3b0c..8bcf9fe 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -1,19 +1,36 @@ package db import ( + "fmt" "time" ) type Component struct { - ID uint `json:"id" gorm:"many2many:incident_component_relation"` - Name string `json:"name,omitempty"` - Attrs []ComponentAttr `json:"attrs,omitempty"` + ID uint `json:"id"` + Name string `json:"name,omitempty"` + Attrs []ComponentAttr `json:"attributes,omitempty"` + Incidents []*Incident `json:"incidents,omitempty" gorm:"many2many:incident_component_relation"` } func (c *Component) TableName() string { return "component" } +func (c *Component) PrintAttrs() string { + var category, region, compType string + for _, a := range c.Attrs { + switch a.Name { + case "category": + category = a.Value + case "region": + region = a.Value + case "type": + compType = a.Value + } + } + return fmt.Sprintf("%s (%s, %s, %s)", c.Name, category, region, compType) +} + type ComponentAttr struct { ID uint `json:"-"` ComponentID uint `json:"-"` @@ -33,7 +50,7 @@ type Incident struct { EndDate *time.Time `json:"end_date"` Impact *int `json:"impact" gorm:"not null"` Statuses []IncidentStatus `json:"updates"` - System *bool `json:"system" gorm:"not null"` + System bool `json:"system" gorm:"not null"` Components []Component `json:"components" gorm:"many2many:incident_component_relation"` } @@ -41,6 +58,10 @@ func (in *Incident) TableName() string { return "incident" } +func (in *Incident) Link() string { + return fmt.Sprintf("%s", in.ID, *in.Text) +} + // IncidentStatus is a db table representation. type IncidentStatus struct { ID uint `json:"id"` diff --git a/openapi.yaml b/openapi.yaml index 5ae7bd2..d773f65 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -405,7 +405,9 @@ components: type: string example: "Object Storage Service" attrs: - $ref: '#/components/schemas/ComponentAttrV1' + type: array + items: + $ref: '#/components/schemas/ComponentAttrV1' incidents: $ref: '#/components/schemas/IncidentsV1' ComponentPostV1: From 5b59a3bd69c684abe2b4fbe822559e773ca76690 Mon Sep 17 00:00:00 2001 From: Sergei Martynov Date: Thu, 24 Oct 2024 13:26:58 +0200 Subject: [PATCH 2/9] refactoring v2 --- internal/api/api.go | 2 +- internal/api/handlers.go | 275 ---------------- internal/api/middleware.go | 18 +- internal/api/routes.go | 52 +-- internal/api/v2/v2.go | 298 ++++++++++++++++++ .../api/{handlers_test.go => v2/v2_test.go} | 76 +++-- internal/db/db.go | 6 +- 7 files changed, 383 insertions(+), 344 deletions(-) delete mode 100644 internal/api/handlers.go rename internal/api/{handlers_test.go => v2/v2_test.go} (69%) diff --git a/internal/api/api.go b/internal/api/api.go index a21dfde..3f4b512 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -27,7 +27,7 @@ func New(cfg *conf.Config, log *zap.Logger, database *db.DB) *API { r.NoRoute(errors.Return404) a := &API{r: r, db: database, log: log} - a.initRoutes() + a.InitRoutes() return a } diff --git a/internal/api/handlers.go b/internal/api/handlers.go deleted file mode 100644 index 4836c6b..0000000 --- a/internal/api/handlers.go +++ /dev/null @@ -1,275 +0,0 @@ -package api - -import ( - "errors" - "net/http" - "time" - - "github.com/gin-gonic/gin" - - apiErrors "github.com/stackmon/otc-status-dashboard/internal/api/errors" - "github.com/stackmon/otc-status-dashboard/internal/db" -) - -type IncidentID struct { - ID int `json:"id" uri:"id" binding:"required,gte=0"` -} - -type IncidentData struct { - Title string `json:"title" binding:"required"` - // INCIDENT_IMPACTS = { - // 0: Impact(0, "maintenance", "Scheduled maintenance"), - // 1: Impact(1, "minor", "Minor incident (i.e. performance impact)"), - // 2: Impact(2, "major", "Major incident"), - // 3: Impact(3, "outage", "Service outage"), - // } - Impact *int `json:"impact" binding:"required,gte=0,lte=3"` - Components []int `json:"components" binding:"required"` - // Datetime format is standard: "2006-01-01T12:00:00Z" - StartDate time.Time `json:"start_date" binding:"required"` - EndDate *time.Time `json:"end_date,omitempty"` - System *bool `json:"system,omitempty"` - Updates []db.IncidentStatus `json:"updates,omitempty"` -} - -type Incident struct { - IncidentID - IncidentData -} - -func (a *API) GetIncidentsHandler(c *gin.Context) { - r, err := a.db.GetIncidents() - if err != nil { - apiErrors.RaiseInternalErr(c, err) - return - } - - incidents := make([]*Incident, len(r)) - for i, inc := range r { - components := make([]int, len(inc.Components)) - for ind, comp := range inc.Components { - components[ind] = int(comp.ID) - } - - incidents[i] = &Incident{ - IncidentID: IncidentID{int(inc.ID)}, - IncidentData: IncidentData{ - Title: *inc.Text, - Impact: inc.Impact, - Components: components, - StartDate: *inc.StartDate, - EndDate: inc.EndDate, - System: &inc.System, - Updates: inc.Statuses, - }, - } - } - - c.JSON(http.StatusOK, gin.H{"data": incidents}) -} - -func (a *API) GetIncidentHandler(c *gin.Context) { - var incID IncidentID - if err := c.ShouldBindUri(&incID); err != nil { - apiErrors.RaiseBadRequestErr(c, err) - return - } - - r, err := a.db.GetIncident(incID.ID) - if err != nil { - if errors.Is(err, db.ErrDBIncidentDSNotExist) { - apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrIncidentDSNotExist) - return - } - apiErrors.RaiseInternalErr(c, err) - return - } - - components := make([]int, len(r.Components)) - for i, comp := range r.Components { - components[i] = int(comp.ID) - } - - incData := IncidentData{ - Title: *r.Text, - Impact: r.Impact, - Components: components, - StartDate: *r.StartDate, - EndDate: r.EndDate, - System: &r.System, - Updates: r.Statuses, - } - - c.JSON(http.StatusOK, &Incident{incID, incData}) -} - -func (a *API) PostIncidentHandler(c *gin.Context) { - var incData IncidentData - if err := c.ShouldBindBodyWithJSON(&incData); err != nil { - apiErrors.RaiseBadRequestErr(c, err) - return - } - - components := make([]db.Component, len(incData.Components)) - for i, comp := range incData.Components { - components[i] = db.Component{ID: uint(comp)} - } - - dbInc := db.Incident{ - Text: &incData.Title, - StartDate: &incData.StartDate, - EndDate: incData.EndDate, - Impact: incData.Impact, - System: *incData.System, - Components: components, - } - - incidentID, err := a.db.SaveIncident(&dbInc) - if err != nil { - apiErrors.RaiseInternalErr(c, err) - return - } - - c.JSON(http.StatusOK, Incident{ - IncidentID: IncidentID{int(incidentID)}, - IncidentData: incData, - }) -} - -type PatchIncidentData struct { - Title *string `json:"title,omitempty"` - // INCIDENT_IMPACTS = { - // 0: Impact(0, "maintenance", "Scheduled maintenance"), - // 1: Impact(1, "minor", "Minor incident (i.e. performance impact)"), - // 2: Impact(2, "major", "Major incident"), - // 3: Impact(3, "outage", "Service outage"), - // } - Impact *int `json:"impact,omitempty"` - Components []int `json:"components,omitempty"` - // Datetime format is standard: "2006-01-01T12:00:00Z" - StartDate *time.Time `json:"start_date,omitempty"` - EndDate *time.Time `json:"end_date,omitempty"` - System *bool `json:"system,omitempty"` - Update *db.IncidentStatus `json:"update,omitempty"` -} - -func (a *API) PatchIncidentHandler(c *gin.Context) { - var incID IncidentID - if err := c.ShouldBindUri(&incID); err != nil { - apiErrors.RaiseBadRequestErr(c, err) - return - } - - var incData PatchIncidentData - if err := c.ShouldBindBodyWithJSON(&incData); err != nil { - apiErrors.RaiseBadRequestErr(c, err) - return - } - - var components []db.Component - if len(incData.Components) != 0 { - components = make([]db.Component, len(incData.Components)) - for i, comp := range incData.Components { - components[i] = db.Component{ID: uint(comp)} - } - } - - var statuses []db.IncidentStatus - if incData.Update != nil { - statuses = append(statuses, *incData.Update) - } - - dbInc := db.Incident{ - ID: uint(incID.ID), - Text: incData.Title, - StartDate: incData.StartDate, - EndDate: incData.EndDate, - Impact: incData.Impact, - System: *incData.System, - Components: components, - Statuses: statuses, - } - - err := a.db.ModifyIncident(&dbInc) - if err != nil { - apiErrors.RaiseInternalErr(c, err) - return - } - - c.JSON(http.StatusOK, gin.H{"msg": "incident updated"}) -} - -type Component struct { - ComponentID - Attributes []ComponentAttribute `json:"attributes"` - Name string `json:"name"` -} - -type ComponentID struct { - ID int `json:"id" uri:"id" binding:"required,gte=0"` -} - -type ComponentAttribute struct { - Name string `json:"name"` - Value string `json:"value"` -} - -func (a *API) GetComponentsStatusHandler(c *gin.Context) { - r, err := a.db.GetComponentsWithValues() - if err != nil { - apiErrors.RaiseInternalErr(c, err) - return - } - - c.JSON(http.StatusOK, r) -} - -func (a *API) GetComponentHandler(c *gin.Context) { - var compID ComponentID - if err := c.ShouldBindUri(&compID); err != nil { - apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentInvalidFormat) - return - } - - r, err := a.db.GetComponent(compID.ID) - if err != nil { - if errors.Is(err, db.ErrDBComponentDSNotExist) { - apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrComponentDSNotExist) - return - } - apiErrors.RaiseInternalErr(c, err) - return - } - - c.JSON(http.StatusOK, r) -} - -// PostComponentStatusHandler creates a new component. -// TODO: copy-paste from the legacy, it's implemented, but only for API. We should discuss about this functionality. -// -// Process component status update and open new incident if required: -// -// - current active maintenance for the component - do nothing -// - current active incident for the component - do nothing -// - current active incident NOT for the component - add component into -// the list of affected components -// - no active incidents - create new one -// - current active incident for the component and requested -// impact > current impact - run handling: -// -// If a component exists in an incident, but the requested -// impact is higher than the current one, then the component -// will be moved to another incident if it exists with the -// requested impact, otherwise a new incident will be created -// and the component will be moved to the new incident. -// If there is only one component in an incident, and an -// incident with the requested impact does not exist, -// then the impact of the incident will be changed to a higher -// one, otherwise the component will be moved to an existing -// incident with the requested impact, and the current incident -// will be closed by the system. -// The movement of a component and the closure of an incident -// will be reflected in the incident statuses. -func (a *API) PostComponentStatusHandler(c *gin.Context) { - c.JSON(http.StatusOK, map[string]string{"status": "in development"}) -} diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 4036f6d..d2bfc58 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -10,7 +10,7 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" - errors2 "github.com/stackmon/otc-status-dashboard/internal/api/errors" + apiErrors "github.com/stackmon/otc-status-dashboard/internal/api/errors" ) func (a *API) ValidateComponentsMW() gin.HandlerFunc { @@ -22,7 +22,7 @@ func (a *API) ValidateComponentsMW() gin.HandlerFunc { var components Components if err := c.ShouldBindBodyWithJSON(&components); err != nil { - errors2.RaiseBadRequestErr(c, fmt.Errorf("%w: %w", errors2.ErrComponentInvalidFormat, err)) + apiErrors.RaiseBadRequestErr(c, fmt.Errorf("%w: %w", apiErrors.ErrComponentInvalidFormat, err)) return } @@ -30,10 +30,10 @@ func (a *API) ValidateComponentsMW() gin.HandlerFunc { // We should check, that all components are presented in our db. err := a.IsPresentComponent(components.Components) if err != nil { - if errors.Is(err, errors2.ErrComponentDSNotExist) { - errors2.RaiseBadRequestErr(c, err) + if errors.Is(err, apiErrors.ErrComponentDSNotExist) { + apiErrors.RaiseBadRequestErr(c, err) } else { - errors2.RaiseInternalErr(c, err) + apiErrors.RaiseInternalErr(c, err) } } c.Next() @@ -48,7 +48,7 @@ func (a *API) IsPresentComponent(components []int) error { for _, comp := range components { if _, ok := dbComps[comp]; !ok { - return errors2.ErrComponentDSNotExist + return apiErrors.ErrComponentDSNotExist } } @@ -67,10 +67,10 @@ func ErrorHandle() gin.HandlerFunc { var err error err = c.Errors.Last() if status >= http.StatusInternalServerError { - err = errors2.ErrInternalError + err = apiErrors.ErrInternalError } - c.JSON(-1, errors2.ReturnError(err)) + c.JSON(-1, apiErrors.ReturnError(err)) } } @@ -100,7 +100,7 @@ func Logger(log *zap.Logger) gin.HandlerFunc { switch { case c.Writer.Status() >= http.StatusInternalServerError: - msg := fmt.Sprintf("panic was recovered, %s", errors2.ErrInternalError) + msg := fmt.Sprintf("panic was recovered, %s", apiErrors.ErrInternalError) if c.Errors.Last() != nil { msg = c.Errors.Last().Error() } diff --git a/internal/api/routes.go b/internal/api/routes.go index e0ae24a..60d76ff 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -1,13 +1,16 @@ package api -import v1 "github.com/stackmon/otc-status-dashboard/internal/api/v1" +import ( + v1 "github.com/stackmon/otc-status-dashboard/internal/api/v1" + v2 "github.com/stackmon/otc-status-dashboard/internal/api/v2" +) const ( v1Group = "v1" v2Group = "v2" ) -func (a *API) initRoutes() { +func (a *API) InitRoutes() { v1Api := a.r.Group(v1Group) { v1Api.GET("component_status", v1.GetComponentsStatusHandler(a.db, a.log)) @@ -15,27 +18,28 @@ func (a *API) initRoutes() { v1Api.GET("incidents", v1.GetIncidentsHandler(a.db, a.log)) } - //nolint:gocritic + // setup v2 group routing - //v2 := a.router.Group(v2Group) - //{ - // v2.GET("components", a.GetComponentsStatusHandler) - // v2.GET("components/:id", a.GetComponentHandler) - // v2.GET("component_status", a.GetComponentsStatusHandler) - // v2.POST("component_status", a.PostComponentStatusHandler) - // - // v2.GET("incidents", a.GetIncidentsHandler) - // v2.POST("incidents", a.ValidateComponentsMW(), a.PostIncidentHandler) - // v2.GET("incidents/:id", a.GetIncidentHandler) - // v2.PATCH("incidents/:id", a.ValidateComponentsMW(), a.PatchIncidentHandler) - // - // v2.GET("rss") - // v2.GET("history") - // v2.GET("availability") - // v2.GET("/separate//") - > investigate it!!! - // - // v2.GET("/login/:name") - // v2.GET("/auth/:name") - // v2.GET("/logout") - //} + v2Api := a.r.Group(v2Group) + { + v2Api.GET("components", v2.GetComponentsStatusHandler(a.db, a.log)) + v2Api.GET("components/:id", v2.GetComponentHandler(a.db, a.log)) + v2Api.GET("component_status", v2.GetComponentsStatusHandler(a.db, a.log)) + v2Api.POST("component_status", v2.PostComponentStatusHandler(a.db, a.log)) + + v2Api.GET("incidents", v2.GetIncidentsHandler(a.db, a.log)) + v2Api.POST("incidents", a.ValidateComponentsMW(), v2.PostIncidentHandler(a.db, a.log)) + v2Api.GET("incidents/:id", v2.GetIncidentHandler(a.db, a.log)) + v2Api.PATCH("incidents/:id", a.ValidateComponentsMW(), v2.PatchIncidentHandler(a.db, a.log)) + + //nolint:gocritic + //v2Api.GET("rss") + //v2Api.GET("history") + //v2Api.GET("availability") + //v2Api.GET("/separate//") - > investigate it!!! + // + //v2Api.GET("/login/:name") + //v2Api.GET("/auth/:name") + //v2Api.GET("/logout") + } } diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go index 5ec3cc8..20f6f84 100644 --- a/internal/api/v2/v2.go +++ b/internal/api/v2/v2.go @@ -1 +1,299 @@ package v2 + +import ( + "errors" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + apiErrors "github.com/stackmon/otc-status-dashboard/internal/api/errors" + "github.com/stackmon/otc-status-dashboard/internal/db" +) + +type IncidentID struct { + ID int `json:"id" uri:"id" binding:"required,gte=0"` +} + +type IncidentData struct { + Title string `json:"title" binding:"required"` + // INCIDENT_IMPACTS = { + // 0: Impact(0, "maintenance", "Scheduled maintenance"), + // 1: Impact(1, "minor", "Minor incident (i.e. performance impact)"), + // 2: Impact(2, "major", "Major incident"), + // 3: Impact(3, "outage", "Service outage"), + // } + Impact *int `json:"impact" binding:"required,gte=0,lte=3"` + Components []int `json:"components" binding:"required"` + // Datetime format is standard: "2006-01-01T12:00:00Z" + StartDate time.Time `json:"start_date" binding:"required"` + EndDate *time.Time `json:"end_date,omitempty"` + System *bool `json:"system,omitempty"` + Updates []db.IncidentStatus `json:"updates,omitempty"` +} + +type Incident struct { + IncidentID + IncidentData +} + +func GetIncidentsHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve incidents") + r, err := dbInst.GetIncidents() + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + incidents := make([]*Incident, len(r)) + for i, inc := range r { + components := make([]int, len(inc.Components)) + for ind, comp := range inc.Components { + components[ind] = int(comp.ID) + } + + incidents[i] = &Incident{ + IncidentID: IncidentID{int(inc.ID)}, + IncidentData: IncidentData{ + Title: *inc.Text, + Impact: inc.Impact, + Components: components, + StartDate: *inc.StartDate, + EndDate: inc.EndDate, + System: &inc.System, + Updates: inc.Statuses, + }, + } + } + + c.JSON(http.StatusOK, gin.H{"data": incidents}) + } +} + +func GetIncidentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve incident") + var incID IncidentID + if err := c.ShouldBindUri(&incID); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + r, err := dbInst.GetIncident(incID.ID) + if err != nil { + if errors.Is(err, db.ErrDBIncidentDSNotExist) { + apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrIncidentDSNotExist) + return + } + apiErrors.RaiseInternalErr(c, err) + return + } + + components := make([]int, len(r.Components)) + for i, comp := range r.Components { + components[i] = int(comp.ID) + } + + incData := IncidentData{ + Title: *r.Text, + Impact: r.Impact, + Components: components, + StartDate: *r.StartDate, + EndDate: r.EndDate, + System: &r.System, + Updates: r.Statuses, + } + + c.JSON(http.StatusOK, &Incident{incID, incData}) + } +} + +func PostIncidentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("create an incident") + var incData IncidentData + if err := c.ShouldBindBodyWithJSON(&incData); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + components := make([]db.Component, len(incData.Components)) + for i, comp := range incData.Components { + components[i] = db.Component{ID: uint(comp)} + } + + dbInc := db.Incident{ + Text: &incData.Title, + StartDate: &incData.StartDate, + EndDate: incData.EndDate, + Impact: incData.Impact, + System: *incData.System, + Components: components, + } + + incidentID, err := dbInst.SaveIncident(&dbInc) + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusOK, Incident{ + IncidentID: IncidentID{int(incidentID)}, + IncidentData: incData, + }) + } +} + +type PatchIncidentData struct { + Title *string `json:"title,omitempty"` + // INCIDENT_IMPACTS = { + // 0: Impact(0, "maintenance", "Scheduled maintenance"), + // 1: Impact(1, "minor", "Minor incident (i.e. performance impact)"), + // 2: Impact(2, "major", "Major incident"), + // 3: Impact(3, "outage", "Service outage"), + // } + Impact *int `json:"impact,omitempty"` + Components []int `json:"components,omitempty"` + // Datetime format is standard: "2006-01-01T12:00:00Z" + StartDate *time.Time `json:"start_date,omitempty"` + EndDate *time.Time `json:"end_date,omitempty"` + System *bool `json:"system,omitempty"` + Update *db.IncidentStatus `json:"update,omitempty"` +} + +func PatchIncidentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("update incident") + + var incID IncidentID + if err := c.ShouldBindUri(&incID); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + var incData PatchIncidentData + if err := c.ShouldBindBodyWithJSON(&incData); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + var components []db.Component + if len(incData.Components) != 0 { + components = make([]db.Component, len(incData.Components)) + for i, comp := range incData.Components { + components[i] = db.Component{ID: uint(comp)} + } + } + + var statuses []db.IncidentStatus + if incData.Update != nil { + statuses = append(statuses, *incData.Update) + } + + dbInc := db.Incident{ + ID: uint(incID.ID), + Text: incData.Title, + StartDate: incData.StartDate, + EndDate: incData.EndDate, + Impact: incData.Impact, + System: *incData.System, + Components: components, + Statuses: statuses, + } + + err := dbInst.ModifyIncident(&dbInc) + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{"msg": "incident updated"}) + } +} + +type Component struct { + ComponentID + Attributes []ComponentAttribute `json:"attributes"` + Name string `json:"name"` +} + +type ComponentID struct { + ID int `json:"id" uri:"id" binding:"required,gte=0"` +} + +type ComponentAttribute struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func GetComponentsStatusHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve components") + + r, err := dbInst.GetComponentsWithValues() + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusOK, r) + } +} + +func GetComponentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve component") + + var compID ComponentID + if err := c.ShouldBindUri(&compID); err != nil { + apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentInvalidFormat) + return + } + + r, err := dbInst.GetComponent(compID.ID) + if err != nil { + if errors.Is(err, db.ErrDBComponentDSNotExist) { + apiErrors.RaiseStatusNotFoundErr(c, apiErrors.ErrComponentDSNotExist) + return + } + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusOK, r) + } +} + +// PostComponentStatusHandler creates a new component. +// TODO: copy-paste from the legacy, it's implemented, but only for API. We should discuss about this functionality. +// +// Process component status update and open new incident if required: +// +// - current active maintenance for the component - do nothing +// - current active incident for the component - do nothing +// - current active incident NOT for the component - add component into +// the list of affected components +// - no active incidents - create new one +// - current active incident for the component and requested +// impact > current impact - run handling: +// +// If a component exists in an incident, but the requested +// impact is higher than the current one, then the component +// will be moved to another incident if it exists with the +// requested impact, otherwise a new incident will be created +// and the component will be moved to the new incident. +// If there is only one component in an incident, and an +// incident with the requested impact does not exist, +// then the impact of the incident will be changed to a higher +// one, otherwise the component will be moved to an existing +// incident with the requested impact, and the current incident +// will be closed by the system. +// The movement of a component and the closure of an incident +// will be reflected in the incident statuses. +func PostComponentStatusHandler(_ *db.DB, _ *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + c.JSON(http.StatusOK, map[string]string{"status": "in development"}) + } +} diff --git a/internal/api/handlers_test.go b/internal/api/v2/v2_test.go similarity index 69% rename from internal/api/handlers_test.go rename to internal/api/v2/v2_test.go index a3c4274..3f2ac05 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/v2/v2_test.go @@ -1,4 +1,4 @@ -package api +package v2 import ( "database/sql/driver" @@ -12,46 +12,78 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" "github.com/stackmon/otc-status-dashboard/internal/api/errors" "github.com/stackmon/otc-status-dashboard/internal/db" ) -var testAPI *API -var mock sqlmock.Sqlmock - func TestGetIncidentsHandler(t *testing.T) { - initTests(t) + r, m := initTests(t) str := "2024-09-01T11:45:26.371Z" testTime, err := time.Parse(time.RFC3339, str) require.NoError(t, err) - prepareDB(t, testTime) + prepareDB(t, m, testTime) var response = `{"data":[{"id":1,"title":"Incident title","impact":0,"components":[150],"start_date":"%s","system":false,"updates":[{"id":1,"status":"resolved","text":"Issue solved.","timestamp":"%s"}]}]}` w := httptest.NewRecorder() - req, _ := http.NewRequest(http.MethodGet, "/v1/incidents", nil) - testAPI.r.ServeHTTP(w, req) + req, _ := http.NewRequest(http.MethodGet, "/v2/incidents", nil) - assert.Equal(t, 200, w.Code) + r.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) assert.Equal(t, fmt.Sprintf(response, str, str), w.Body.String()) } func TestReturn404Handler(t *testing.T) { - initTests(t) + r, _ := initTests(t) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/anyendpoint", nil) - testAPI.r.ServeHTTP(w, req) + r.ServeHTTP(w, req) assert.Equal(t, 404, w.Code) assert.Equal(t, `{"errMsg":"page not found"}`, w.Body.String()) } -func prepareDB(t *testing.T, testTime time.Time) { +func initTests(t *testing.T) (*gin.Engine, sqlmock.Sqlmock) { + t.Helper() + + t.Log("start initialisation") + d, m, err := db.NewWithMock() + require.NoError(t, err) + + gin.SetMode(gin.TestMode) + r := gin.Default() + r.NoRoute(errors.Return404) + + log, _ := zap.NewDevelopment() + initRoutes(t, r, d, log) + + return r, m +} + +func initRoutes(t *testing.T, c *gin.Engine, dbInst *db.DB, log *zap.Logger) { + t.Helper() + + v2Api := c.Group("v2") + { + v2Api.GET("components", GetComponentsStatusHandler(dbInst, log)) + v2Api.GET("components/:id", GetComponentHandler(dbInst, log)) + v2Api.GET("component_status", GetComponentsStatusHandler(dbInst, log)) + v2Api.POST("component_status", PostComponentStatusHandler(dbInst, log)) + + v2Api.GET("incidents", GetIncidentsHandler(dbInst, log)) + v2Api.POST("incidents", PostIncidentHandler(dbInst, log)) + v2Api.GET("incidents/:id", GetIncidentHandler(dbInst, log)) + v2Api.PATCH("incidents/:id", PatchIncidentHandler(dbInst, log)) + } +} + +func prepareDB(t *testing.T, mock sqlmock.Sqlmock, testTime time.Time) { t.Helper() rows := sqlmock.NewRows([]string{"id", "text", "start_date", "end_date", "impact", "system"}). @@ -87,23 +119,3 @@ func prepareDB(t *testing.T, testTime time.Time) { mock.NewRowsWithColumnDefinition() } - -func initTests(t *testing.T) { - t.Helper() - - if testAPI != nil && mock != nil { - t.Log("testAPI and mock are initialized") - } - - t.Log("start initialisation") - r := gin.Default() - r.Use(ErrorHandle()) - r.NoRoute(errors.Return404) - - d, m, err := db.NewWithMock() - require.NoError(t, err) - - testAPI = &API{r: r, db: d} - testAPI.initRoutes() - mock = m -} diff --git a/internal/db/db.go b/internal/db/db.go index 4842318..cb1ad7f 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -44,9 +44,9 @@ type IncidentsParams struct { func (db *DB) GetIncidents(params ...*IncidentsParams) ([]*Incident, error) { var incidents []*Incident - var param *IncidentsParams - if len(params) != 0 && params[0] != nil { - param = params[0] + var param IncidentsParams + if params != nil && params[0] != nil { + param = *params[0] } r := db.g.Debug().Model(&Incident{}). From caaec99eb0cec6ddf61fcb9579dc47f7f9f90860 Mon Sep 17 00:00:00 2001 From: Sergei Martynov Date: Tue, 29 Oct 2024 14:30:28 +0100 Subject: [PATCH 3/9] added acc and unit tests --- Makefile | 8 +- go.mod | 58 ++++- go.sum | 142 ++++++++++++ internal/api/errors/errors.go | 6 +- internal/api/routes.go | 5 +- internal/api/v1/v1.go | 36 ++- internal/api/v1/v1_test.go | 35 +++ internal/api/v2/v2.go | 131 ++++++++--- internal/api/v2/v2_test.go | 6 +- internal/db/db.go | 47 +++- internal/db/errors.go | 1 + openapi.yaml | 4 +- tests/main_test.go | 99 ++++++++ tests/testdata/dumb_test.sql | 416 ++++++++++++++++++++++++++++++++++ tests/v1_test.go | 105 +++++++++ 15 files changed, 1041 insertions(+), 58 deletions(-) create mode 100644 internal/api/v1/v1_test.go create mode 100644 tests/main_test.go create mode 100644 tests/testdata/dumb_test.sql create mode 100644 tests/v1_test.go diff --git a/Makefile b/Makefile index 946c88d..1f4a74a 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,12 @@ SHELL=/bin/bash SD_DB?="postgresql://pg:pass@localhost:5432/status_dashboard?sslmode=disable" test: - @echo running tests - go test ./... -count 1 + @echo running unit tests + go test ./internal/... -count 1 + +integ_test: + @echo running integrational tests with docker and db + go test ./tests/... -count 1 build: @echo build app diff --git a/go.mod b/go.mod index fb082d2..8537f74 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 + github.com/lib/pq v1.10.9 + github.com/ory/dockertest/v3 v3.11.0 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 gorm.io/driver/postgres v1.5.9 @@ -15,18 +17,40 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/bytedance/sonic v1.12.1 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/containerd/continuity v0.4.3 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v27.3.1+incompatible // indirect + github.com/docker/docker v27.3.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect @@ -34,25 +58,57 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/testcontainers/testcontainers-go v0.34.0 // indirect + github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.9.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.18.0 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 23b4d2f..a1f327c 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,63 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= +github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -29,11 +66,22 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -53,7 +101,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -71,20 +123,61 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runc v1.2.0 h1:qke7ZVCmJcKrJVY2iHJVC+0kql9uYdkusOPsQOOeBw4= +github.com/opencontainers/runc v1.2.0/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= +github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= +github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -98,11 +191,38 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= +github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 h1:c51aBXT3v2HEBVarmaBnsKzvgZjC5amn0qsj8Naqi50= +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0/go.mod h1:EWP75ogLQU4M4L8U+20mFipjV4WIR9WtlMXSB6/wiuc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -118,29 +238,46 @@ golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -149,9 +286,12 @@ golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= @@ -161,6 +301,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/api/errors/errors.go b/internal/api/errors/errors.go index 4c02d01..62ef23c 100644 --- a/internal/api/errors/errors.go +++ b/internal/api/errors/errors.go @@ -26,8 +26,12 @@ var ErrInternalError = errors.New("internal server error") var ErrIncidentDSNotExist = errors.New("incident does not exist") var ErrComponentDSNotExist = errors.New("component does not exist") +var ErrComponentExist = errors.New("component already exists") var ErrComponentInvalidFormat = errors.New("component invalid format") -var ErrComponentRegionAttrMissing = errors.New("component attribute region missing") +var ErrComponentAttrInvalidFormat = errors.New("component attribute has invalid format") +var ErrComponentRegionAttrMissing = errors.New("component attribute region is missing or invalid") +var ErrComponentTypeAttrMissing = errors.New("component attribute type is missing or invalid") +var ErrComponentCategoryAttrMissing = errors.New("component attribute category is missing or invalid") func Return404(c *gin.Context) { c.JSON(http.StatusNotFound, ReturnError(ErrPageNotFound)) diff --git a/internal/api/routes.go b/internal/api/routes.go index 60d76ff..994c134 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -22,10 +22,9 @@ func (a *API) InitRoutes() { // setup v2 group routing v2Api := a.r.Group(v2Group) { - v2Api.GET("components", v2.GetComponentsStatusHandler(a.db, a.log)) + v2Api.GET("components", v2.GetComponentsHandler(a.db, a.log)) + v2Api.POST("components", v2.PostComponentHandler(a.db, a.log)) v2Api.GET("components/:id", v2.GetComponentHandler(a.db, a.log)) - v2Api.GET("component_status", v2.GetComponentsStatusHandler(a.db, a.log)) - v2Api.POST("component_status", v2.PostComponentStatusHandler(a.db, a.log)) v2Api.GET("incidents", v2.GetIncidentsHandler(a.db, a.log)) v2Api.POST("incidents", a.ValidateComponentsMW(), v2.PostIncidentHandler(a.db, a.log)) diff --git a/internal/api/v1/v1.go b/internal/api/v1/v1.go index be3afcf..53a67b7 100644 --- a/internal/api/v1/v1.go +++ b/internal/api/v1/v1.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -32,8 +33,8 @@ type IncidentData struct { Impact *int `json:"impact" binding:"required,gte=0,lte=3"` // datetime format is "2006-01-01 12:00" StartDate SD2Time `json:"start_date" binding:"required"` - EndDate *SD2Time `json:"end_date,omitempty"` - Updates []*IncidentStatus `json:"updates,omitempty"` + EndDate *SD2Time `json:"end_date"` + Updates []*IncidentStatus `json:"updates"` } // IncidentStatus is a db table representation. @@ -58,7 +59,16 @@ func (s *SD2Time) MarshalJSON() ([]byte, error) { } func (s *SD2Time) UnmarshalJSON(data []byte) error { - t, err := time.Parse(timeLayout, string(data)) + strData := string(data) + if strData == "null" { + *s = SD2Time{} + return nil + } + if strings.HasPrefix(strData, "\"") { + runes := []rune(strData) + strData = string(runes[1 : len(runes)-1]) + } + t, err := time.Parse(timeLayout, strData) if err != nil { return err } @@ -86,14 +96,19 @@ func GetIncidentsHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { } } - endDate := SD2Time(*inc.EndDate) + var endDate *SD2Time + if inc.EndDate != nil { + sd2T := SD2Time(*inc.EndDate) + endDate = &sd2T + } + incidents[i] = &Incident{ IncidentID: IncidentID{int(inc.ID)}, IncidentData: IncidentData{ Text: *inc.Text, Impact: inc.Impact, StartDate: SD2Time(*inc.StartDate), - EndDate: &endDate, + EndDate: endDate, Updates: updates, }, } @@ -142,9 +157,10 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { incidents := make([]*Incident, len(component.Incidents)) for i, inc := range component.Incidents { - var endDate SD2Time + var endDate *SD2Time if inc.EndDate != nil { - endDate = SD2Time(*inc.EndDate) + sd2T := SD2Time(*inc.EndDate) + endDate = &sd2T } newInc := &Incident{ @@ -153,7 +169,7 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { Text: *inc.Text, Impact: inc.Impact, StartDate: SD2Time(*inc.StartDate), - EndDate: &endDate, + EndDate: endDate, Updates: nil, }, } @@ -220,7 +236,7 @@ type ComponentStatusPost struct { func PostComponentStatusHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { //nolint:gocognit return func(c *gin.Context) { var inComponent ComponentStatusPost - attrs, err := extractComponentAttr(c, &inComponent) + attr, err := extractComponentAttr(c, &inComponent) if err != nil { apiErrors.RaiseBadRequestErr(c, err) return @@ -229,7 +245,7 @@ func PostComponentStatusHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFu log := logger.With(zap.Any("component", inComponent)) log.Info("get component from name and attributes") - storedComponent, err := dbInst.GetComponentFromNameAttrs(inComponent.Name, attrs) + storedComponent, err := dbInst.GetComponentFromNameAttrs(inComponent.Name, attr) if err != nil { if errors.Is(err, db.ErrDBComponentDSNotExist) { apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentDSNotExist) diff --git a/internal/api/v1/v1_test.go b/internal/api/v1/v1_test.go new file mode 100644 index 0000000..becb8f8 --- /dev/null +++ b/internal/api/v1/v1_test.go @@ -0,0 +1,35 @@ +package v1 + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCustomTimeFormat(t *testing.T) { + timeRFC3339Str := "2024-09-01T11:45:26.371Z" + parsedTime, err := time.Parse(time.RFC3339, timeRFC3339Str) + require.NoError(t, err) + inc := &Incident{ + IncidentData: IncidentData{ + StartDate: SD2Time(parsedTime), + EndDate: nil, + }, + } + + data, err := json.Marshal(inc) + require.NoError(t, err) + assert.Equal(t, "{\"id\":0,\"text\":\"\",\"impact\":null,\"start_date\":\"2024-09-01 11:45\",\"end_date\":null,\"updates\":null}", string(data)) + + inc = &Incident{} + err = json.Unmarshal(data, &inc) + require.NoError(t, err) + assert.Equal(t, parsedTime.YearDay(), time.Time(inc.StartDate).YearDay()) + assert.Equal(t, parsedTime.Hour(), time.Time(inc.StartDate).Hour()) + assert.Equal(t, parsedTime.Minute(), time.Time(inc.StartDate).Minute()) + assert.NotEqual(t, parsedTime.Second(), time.Time(inc.StartDate).Second()) + assert.Nil(t, inc.EndDate) +} diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go index 20f6f84..1869f12 100644 --- a/internal/api/v2/v2.go +++ b/internal/api/v2/v2.go @@ -110,6 +110,32 @@ func GetIncidentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { } } +// PostIncidentHandler creates an incident. +// TODO: copy-paste from the legacy, it's implemented, but only for API. We should discuss about this functionality. +// +// Process component status update and open new incident if required: +// +// - current active maintenance for the component - do nothing +// - current active incident for the component - do nothing +// - current active incident NOT for the component - add component into +// the list of affected components +// - no active incidents - create new one +// - current active incident for the component and requested +// impact > current impact - run handling: +// +// If a component exists in an incident, but the requested +// impact is higher than the current one, then the component +// will be moved to another incident if it exists with the +// requested impact, otherwise a new incident will be created +// and the component will be moved to the new incident. +// If there is only one component in an incident, and an +// incident with the requested impact does not exist, +// then the impact of the incident will be changed to a higher +// one, otherwise the component will be moved to an existing +// incident with the requested impact, and the current incident +// will be closed by the system. +// The movement of a component and the closure of an incident +// will be reflected in the incident statuses. func PostIncidentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { logger.Debug("create an incident") @@ -223,12 +249,24 @@ type ComponentID struct { ID int `json:"id" uri:"id" binding:"required,gte=0"` } +// ComponentAttribute provides additional attributes for component. +// Available list of possible attributes are: +// 1. type +// 2. region +// 3. category +// All of them are required for creation. type ComponentAttribute struct { Name string `json:"name"` Value string `json:"value"` } -func GetComponentsStatusHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { +var availableAttrs = map[string]struct{}{ //nolint:gochecknoglobals + "type": {}, + "region": {}, + "category": {}, +} + +func GetComponentsHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { logger.Debug("retrieve components") @@ -266,34 +304,69 @@ func GetComponentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { } } -// PostComponentStatusHandler creates a new component. -// TODO: copy-paste from the legacy, it's implemented, but only for API. We should discuss about this functionality. -// -// Process component status update and open new incident if required: -// -// - current active maintenance for the component - do nothing -// - current active incident for the component - do nothing -// - current active incident NOT for the component - add component into -// the list of affected components -// - no active incidents - create new one -// - current active incident for the component and requested -// impact > current impact - run handling: -// -// If a component exists in an incident, but the requested -// impact is higher than the current one, then the component -// will be moved to another incident if it exists with the -// requested impact, otherwise a new incident will be created -// and the component will be moved to the new incident. -// If there is only one component in an incident, and an -// incident with the requested impact does not exist, -// then the impact of the incident will be changed to a higher -// one, otherwise the component will be moved to an existing -// incident with the requested impact, and the current incident -// will be closed by the system. -// The movement of a component and the closure of an incident -// will be reflected in the incident statuses. -func PostComponentStatusHandler(_ *db.DB, _ *zap.Logger) gin.HandlerFunc { +type PostComponentData struct { + Attributes []ComponentAttribute `json:"attrs" binding:"required"` + Name string `json:"name" binding:"required"` +} + +// PostComponentHandler creates a new component. +func PostComponentHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { - c.JSON(http.StatusOK, map[string]string{"status": "in development"}) + logger.Debug("create a component") + + var component PostComponentData + if err := c.ShouldBindBodyWithJSON(&component); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + if err := checkComponentAttrs(component.Attributes); err != nil { + apiErrors.RaiseBadRequestErr(c, err) + return + } + + attrs := make([]db.ComponentAttr, len(component.Attributes)) + for i, attr := range component.Attributes { + attrs[i] = db.ComponentAttr{ + Name: attr.Name, + Value: attr.Value, + } + } + + compDB := &db.Component{ + Name: component.Name, + Attrs: attrs, + } + + componentID, err := dbInst.SaveComponent(compDB) + if err != nil { + if errors.Is(err, db.ErrDBComponentExists) { + apiErrors.RaiseBadRequestErr(c, apiErrors.ErrComponentExist) + } + apiErrors.RaiseInternalErr(c, err) + return + } + + c.JSON(http.StatusCreated, Component{ + ComponentID: ComponentID{int(componentID)}, + Attributes: component.Attributes, + Name: component.Name, + }) + } +} + +func checkComponentAttrs(attrs []ComponentAttribute) error { + //nolint:nolintlint,mnd + // this magic number will be changed in the next iteration + if len(attrs) != 3 { + return apiErrors.ErrComponentAttrInvalidFormat } + for _, attr := range attrs { + _, ok := availableAttrs[attr.Name] + if !ok { + return apiErrors.ErrComponentAttrInvalidFormat + } + } + + return nil } diff --git a/internal/api/v2/v2_test.go b/internal/api/v2/v2_test.go index 3f2ac05..439b170 100644 --- a/internal/api/v2/v2_test.go +++ b/internal/api/v2/v2_test.go @@ -71,10 +71,10 @@ func initRoutes(t *testing.T, c *gin.Engine, dbInst *db.DB, log *zap.Logger) { v2Api := c.Group("v2") { - v2Api.GET("components", GetComponentsStatusHandler(dbInst, log)) + v2Api.GET("components", GetComponentsHandler(dbInst, log)) v2Api.GET("components/:id", GetComponentHandler(dbInst, log)) - v2Api.GET("component_status", GetComponentsStatusHandler(dbInst, log)) - v2Api.POST("component_status", PostComponentStatusHandler(dbInst, log)) + v2Api.GET("component_status", GetComponentsHandler(dbInst, log)) + v2Api.POST("component_status", PostComponentHandler(dbInst, log)) v2Api.GET("incidents", GetIncidentsHandler(dbInst, log)) v2Api.POST("incidents", PostIncidentHandler(dbInst, log)) diff --git a/internal/db/db.go b/internal/db/db.go index cb1ad7f..4e53c83 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -189,27 +189,60 @@ func (db *DB) GetComponentsWithIncidents() ([]Component, error) { return components, nil } -func (db *DB) GetComponentFromNameAttrs(name string, attrs *ComponentAttr) (*Component, error) { +// GetComponentFromNameAttrs returns the Component from its name and region attribute. +func (db *DB) GetComponentFromNameAttrs(name string, attr *ComponentAttr) (*Component, error) { comp := Component{} //nolint:lll // You can reproduce this raw request // select * from component join component_attribute ca on component.id=ca.component_id // where component.id = // (select component.id from component join component_attribute ca on component.id = ca.component_id and ca.value='EU-DE' and component.name='Cloud Container Engine'); - r := db.g.Model(&Component{}). - Where(&Component{Name: name}). - Preload("Attrs", func(db *gorm.DB) *gorm.DB { - return db.Where("name=?", attrs.Name).Where("value=?", attrs.Value) - }). - Preload("Attrs").Find(&comp) + subQuery := db.g.Model(&Component{}). + Select("component.id"). + Joins("JOIN component_attribute ca ON ca.component_id = component.id"). + Where("ca.value = ?", attr.Value). + Where("component.name = ?", name) + r := db.g.Model(&Component{}).Where("name = ?", name). + Where("id = (?)", subQuery). + Preload("Attrs"). + First(&comp) if r.Error != nil { + if errors.Is(r.Error, gorm.ErrRecordNotFound) { + return nil, ErrDBComponentDSNotExist + } return nil, r.Error } return &comp, nil } +func (db *DB) SaveComponent(comp *Component) (uint, error) { + var regIndex int + for i, attr := range comp.Attrs { + if attr.Name == "region" { + regIndex = i + } + } + + _, err := db.GetComponentFromNameAttrs(comp.Name, &comp.Attrs[regIndex]) + if err == nil { + return 0, ErrDBComponentExists + } + + if !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, err + } + + r := db.g.Create(comp) + + if r.Error != nil { + return 0, r.Error + } + + return comp.ID, nil +} + const statusSYSTEM = "SYSTEM" func (db *DB) MoveComponentFromOldToAnotherIncident(comp *Component, incOld, incNew *Incident) (*Incident, error) { diff --git a/internal/db/errors.go b/internal/db/errors.go index 17e3ce8..f9c5382 100644 --- a/internal/db/errors.go +++ b/internal/db/errors.go @@ -3,4 +3,5 @@ package db import "errors" var ErrDBComponentDSNotExist = errors.New("component does not exist") +var ErrDBComponentExists = errors.New("component exists") var ErrDBIncidentDSNotExist = errors.New("incident does not exist") diff --git a/openapi.yaml b/openapi.yaml index d773f65..78f3f5e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -395,7 +395,7 @@ components: required: - id - name - - attrs + - attributes properties: id: type: integer @@ -404,7 +404,7 @@ components: name: type: string example: "Object Storage Service" - attrs: + attributes: type: array items: $ref: '#/components/schemas/ComponentAttrV1' diff --git a/tests/main_test.go b/tests/main_test.go new file mode 100644 index 0000000..cc990d4 --- /dev/null +++ b/tests/main_test.go @@ -0,0 +1,99 @@ +package tests + +import ( + "context" + "fmt" + "log" + "path/filepath" + "testing" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/stackmon/otc-status-dashboard/internal/api" + "github.com/stackmon/otc-status-dashboard/internal/api/errors" + v1 "github.com/stackmon/otc-status-dashboard/internal/api/v1" + "github.com/stackmon/otc-status-dashboard/internal/conf" + "github.com/stackmon/otc-status-dashboard/internal/db" +) + +const ( + pgImage = "postgres:15-alpine" + pgDump = "dumb_test.sql" + pgDumpDir = "testdata" + + dbName = "status_dashboard" + dbUser = "pg" + dbPassword = "pass" +) + +var databaseURL = "postgresql://%s:%s@localhost:%s/%s" + +func TestMain(m *testing.M) { + ctx := context.Background() + container, err := postgres.Run(ctx, + pgImage, + postgres.WithInitScripts(filepath.Join(pgDumpDir, pgDump)), + postgres.WithDatabase(dbName), + postgres.WithUsername(dbUser), + postgres.WithPassword(dbPassword), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(5*time.Second)), + ) + defer func() { + if err = testcontainers.TerminateContainer(container); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + ports, _ := container.Ports(ctx) + port := ports["5432/tcp"][0].HostPort + databaseURL = fmt.Sprintf(databaseURL, dbUser, dbPassword, port, dbName) + + m.Run() +} + +func initTests(t *testing.T) (*gin.Engine, *db.DB) { //nolint:unparam + t.Helper() + t.Log("init structs") + + d, err := db.New(&conf.Config{ + DB: databaseURL, + }) + require.NoError(t, err) + + gin.SetMode(gin.TestMode) + r := gin.Default() + r.NoRoute(errors.Return404) + r.Use(api.ErrorHandle()) + + logger, _ := zap.NewDevelopment() + initRoutesV1(t, r, d, logger) + + return r, d +} + +func initRoutesV1(t *testing.T, c *gin.Engine, dbInst *db.DB, log *zap.Logger) { + t.Helper() + t.Log("init routes") + + v1Api := c.Group("v1") + { + v1Api.GET("component_status", v1.GetComponentsStatusHandler(dbInst, log)) + v1Api.POST("component_status", v1.PostComponentStatusHandler(dbInst, log)) + + v1Api.GET("incidents", v1.GetIncidentsHandler(dbInst, log)) + } +} diff --git a/tests/testdata/dumb_test.sql b/tests/testdata/dumb_test.sql new file mode 100644 index 0000000..13a1610 --- /dev/null +++ b/tests/testdata/dumb_test.sql @@ -0,0 +1,416 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 15.8 (Debian 15.8-1.pgdg120+1) +-- Dumped by pg_dump version 16.4 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: incidentimpactenum; Type: TYPE; Schema: public; Owner: pg +-- + +CREATE TYPE public.incidentimpactenum AS ENUM ( + 'maintenance', + 'minor', + 'major', + 'outage' +); + + +ALTER TYPE public.incidentimpactenum OWNER TO pg; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: component; Type: TABLE; Schema: public; Owner: pg +-- + +CREATE TABLE public.component ( + id integer NOT NULL, + name character varying NOT NULL +); + + +ALTER TABLE public.component OWNER TO pg; + +-- +-- Name: component_attribute; Type: TABLE; Schema: public; Owner: pg +-- + +CREATE TABLE public.component_attribute ( + id integer NOT NULL, + component_id integer, + name character varying NOT NULL, + value character varying NOT NULL +); + + +ALTER TABLE public.component_attribute OWNER TO pg; + +-- +-- Name: component_attribute_id_seq; Type: SEQUENCE; Schema: public; Owner: pg +-- + +CREATE SEQUENCE public.component_attribute_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.component_attribute_id_seq OWNER TO pg; + +-- +-- Name: component_attribute_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg +-- + +ALTER SEQUENCE public.component_attribute_id_seq OWNED BY public.component_attribute.id; + + +-- +-- Name: component_id_seq; Type: SEQUENCE; Schema: public; Owner: pg +-- + +CREATE SEQUENCE public.component_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.component_id_seq OWNER TO pg; + +-- +-- Name: component_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg +-- + +ALTER SEQUENCE public.component_id_seq OWNED BY public.component.id; + + +-- +-- Name: incident; Type: TABLE; Schema: public; Owner: pg +-- + +CREATE TABLE public.incident ( + id integer NOT NULL, + text character varying NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone, + impact smallint NOT NULL, + system boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.incident OWNER TO pg; + +-- +-- Name: incident_component_relation; Type: TABLE; Schema: public; Owner: pg +-- + +CREATE TABLE public.incident_component_relation ( + incident_id integer NOT NULL, + component_id integer NOT NULL +); + + +ALTER TABLE public.incident_component_relation OWNER TO pg; + +-- +-- Name: incident_id_seq; Type: SEQUENCE; Schema: public; Owner: pg +-- + +CREATE SEQUENCE public.incident_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.incident_id_seq OWNER TO pg; + +-- +-- Name: incident_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg +-- + +ALTER SEQUENCE public.incident_id_seq OWNED BY public.incident.id; + + +-- +-- Name: incident_status; Type: TABLE; Schema: public; Owner: pg +-- + +CREATE TABLE public.incident_status ( + id integer NOT NULL, + incident_id integer, + "timestamp" timestamp without time zone NOT NULL, + text character varying NOT NULL, + status character varying NOT NULL +); + + +ALTER TABLE public.incident_status OWNER TO pg; + +-- +-- Name: incident_status_id_seq; Type: SEQUENCE; Schema: public; Owner: pg +-- + +CREATE SEQUENCE public.incident_status_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.incident_status_id_seq OWNER TO pg; + +-- +-- Name: incident_status_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg +-- + +ALTER SEQUENCE public.incident_status_id_seq OWNED BY public.incident_status.id; + + +-- +-- Name: component id; Type: DEFAULT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.component ALTER COLUMN id SET DEFAULT nextval('public.component_id_seq'::regclass); + + +-- +-- Name: component_attribute id; Type: DEFAULT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.component_attribute ALTER COLUMN id SET DEFAULT nextval('public.component_attribute_id_seq'::regclass); + + +-- +-- Name: incident id; Type: DEFAULT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.incident ALTER COLUMN id SET DEFAULT nextval('public.incident_id_seq'::regclass); + + +-- +-- Name: incident_status id; Type: DEFAULT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.incident_status ALTER COLUMN id SET DEFAULT nextval('public.incident_status_id_seq'::regclass); + + +-- +-- Data for Name: component; Type: TABLE DATA; Schema: public; Owner: pg +-- + +COPY public.component (id, name) FROM stdin; +1 Cloud Container Engine +2 Cloud Container Engine +3 Elastic Cloud Server +4 Elastic Cloud Server +5 Distributed Cache Service +6 Distributed Cache Service +\. + + +-- +-- Data for Name: component_attribute; Type: TABLE DATA; Schema: public; Owner: pg +-- + +COPY public.component_attribute (id, component_id, name, value) FROM stdin; +1 1 region EU-DE +2 1 category Container +3 1 type cce +4 2 region EU-NL +5 2 category Container +6 2 type cce +7 3 region EU-DE +8 3 category Compute +9 3 type ecs +10 4 region EU-NL +11 4 category Compute +12 4 type ecs +13 5 region EU-DE +14 5 category Database +15 5 type dcs +16 6 region EU-NL +17 6 category Database +18 6 type dcs +\. + + +-- +-- Data for Name: incident; Type: TABLE DATA; Schema: public; Owner: pg +-- + +COPY public.incident (id, text, start_date, end_date, impact, system) FROM stdin; +1 Opened incident without any update 2024-10-24 10:12:42 \N 1 f +\. + + +-- +-- Data for Name: incident_component_relation; Type: TABLE DATA; Schema: public; Owner: pg +-- + +COPY public.incident_component_relation (incident_id, component_id) FROM stdin; +1 1 +\. + + +-- +-- Data for Name: incident_status; Type: TABLE DATA; Schema: public; Owner: pg +-- + +COPY public.incident_status (id, incident_id, "timestamp", text, status) FROM stdin; +\. + + +-- +-- Name: component_attribute_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg +-- + +SELECT pg_catalog.setval('public.component_attribute_id_seq', 12, true); + + +-- +-- Name: component_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg +-- + +SELECT pg_catalog.setval('public.component_id_seq', 4, true); + + +-- +-- Name: incident_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg +-- + +SELECT pg_catalog.setval('public.incident_id_seq', 1, true); + + +-- +-- Name: incident_status_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg +-- + +SELECT pg_catalog.setval('public.incident_status_id_seq', 1, false); + + +-- +-- Name: component_attribute component_attribute_pkey; Type: CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.component_attribute + ADD CONSTRAINT component_attribute_pkey PRIMARY KEY (id); + + +-- +-- Name: component component_pkey; Type: CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.component + ADD CONSTRAINT component_pkey PRIMARY KEY (id); + + +-- +-- Name: incident incident_pkey; Type: CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.incident + ADD CONSTRAINT incident_pkey PRIMARY KEY (id); + + +-- +-- Name: incident_status incident_status_pkey; Type: CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.incident_status + ADD CONSTRAINT incident_status_pkey PRIMARY KEY (id); + + +-- +-- Name: inc_comp_rel; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE UNIQUE INDEX inc_comp_rel ON public.incident_component_relation USING btree (incident_id, component_id); + + +-- +-- Name: ix_component_attribute_component_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_component_attribute_component_id ON public.component_attribute USING btree (component_id); + + +-- +-- Name: ix_component_attribute_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_component_attribute_id ON public.component_attribute USING btree (id); + + +-- +-- Name: ix_component_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_component_id ON public.component USING btree (id); + + +-- +-- Name: ix_incident_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_incident_id ON public.incident USING btree (id); + + +-- +-- Name: ix_incident_status_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_incident_status_id ON public.incident_status USING btree (id); + + +-- +-- Name: ix_incident_status_incident_id; Type: INDEX; Schema: public; Owner: pg +-- + +CREATE INDEX ix_incident_status_incident_id ON public.incident_status USING btree (incident_id); + + +-- +-- Name: component_attribute component_attribute_component_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.component_attribute + ADD CONSTRAINT component_attribute_component_id_fkey FOREIGN KEY (component_id) REFERENCES public.component(id); + + +-- +-- Name: incident_component_relation incident_component_relation_component_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: pg +-- + +ALTER TABLE ONLY public.incident_component_relation + ADD CONSTRAINT incident_component_relation_component_id_fkey FOREIGN KEY (component_id) REFERENCES public.component(id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/tests/v1_test.go b/tests/v1_test.go new file mode 100644 index 0000000..828dac2 --- /dev/null +++ b/tests/v1_test.go @@ -0,0 +1,105 @@ +package tests + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + v1 "github.com/stackmon/otc-status-dashboard/internal/api/v1" +) + +func TestGetIncidentsHandler(t *testing.T) { + t.Log("start to test GET /v1/incidents") + r, _ := initTests(t) + + var response = `[{"id":1,"text":"Opened incident without any update","impact":1,"start_date":"2024-10-24 10:12","end_date":null,"updates":[]}]` + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/v1/incidents", nil) + + r.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, response, w.Body.String()) +} + +func TestGetComponentsStatusHandler(t *testing.T) { + t.Log("start to test GET /v1/component_status") + r, _ := initTests(t) + + var response = `[{"id":1,"attributes":[{"name":"region","value":"EU-DE"},{"name":"category","value":"Container"},{"name":"type","value":"cce"}],"name":"Cloud Container Engine","incidents":[{"id":1,"text":"Opened incident without any update","impact":1,"start_date":"2024-10-24 10:12","end_date":null,"updates":[]}]},{"id":2,"attributes":[{"name":"region","value":"EU-NL"},{"name":"category","value":"Container"},{"name":"type","value":"cce"}],"name":"Cloud Container Engine","incidents":[]},{"id":3,"attributes":[{"name":"region","value":"EU-DE"},{"name":"category","value":"Compute"},{"name":"type","value":"ecs"}],"name":"Elastic Cloud Server","incidents":[]},{"id":4,"attributes":[{"name":"region","value":"EU-NL"},{"name":"category","value":"Compute"},{"name":"type","value":"ecs"}],"name":"Elastic Cloud Server","incidents":[]},{"id":5,"attributes":[{"name":"region","value":"EU-DE"},{"name":"category","value":"Database"},{"name":"type","value":"dcs"}],"name":"Distributed Cache Service","incidents":[]},{"id":6,"attributes":[{"name":"region","value":"EU-NL"},{"name":"category","value":"Database"},{"name":"type","value":"dcs"}],"name":"Distributed Cache Service","incidents":[]}]` + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/v1/component_status", nil) + + r.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, response, w.Body.String()) +} + +func TestPostComponentsStatusHandler(t *testing.T) { + t.Log("start to test POST requests to /v1/component_status") + r, _ := initTests(t) + + type testCase struct { + ExpectedCode int + Expected string + JSON string + } + + testCases := map[string]*testCase{ + "positive testcase": { + JSON: `{"name":"Distributed Cache Service","text":"Incident","impact": 2,"attributes": [{"name":"region","value":"EU-NL"}]}`, + Expected: `{"id":2,"text":"Incident","impact":2,`, + ExpectedCode: 200, + }, + "negative testcase, region is invalid": { + JSON: `{"name":"Distributed Cache Service","text":"Incident","impact": 2,"attributes": [{"name":"region","value":"EU-NL123"}]}`, + Expected: `{"errMsg":"component does not exist"}`, + ExpectedCode: 400, + }, + "negative testcase, attribute region is missing": { + JSON: `{"name":"Distributed Cache Service","text":"Incident","impact": 2,"attributes": [{"name":"type","value":"dcs"}]}`, + Expected: `{"errMsg":"component attribute region is missing or invalid"}`, + ExpectedCode: 400, + }, + "negative testcase, component name is wrong": { + JSON: `{"name":"New Distributed Cache Service","text":"Incident","impact": 2,"attributes": [{"name":"region","value":"EU-NL"}]}`, + Expected: `{"errMsg":"component does not exist"}`, + ExpectedCode: 400, + }, + //nolint:gocritic + //"negative testcase, the incident with given impact and component already exists": { + // JSON: `{"name":"Cloud Container Engine","text":"Incident","impact": 1,"attributes": [{"name":"region","value":"EU-DE"}]}`, + // Expected: `{"details":"Check your request parameters","existingIncidentId":1,"existingIncidentTitle":"Opened incident without any update","message":"Incident with this the component already exists","targetComponent":{"id":1,"name":"Cloud Container Engine","attributes":[{"name":"region","value":"EU-DE"},{"name":"category","value":"Container"},{"name":"type","value":"cce"}]}}`, + //}, + } + + for title, c := range testCases { + t.Logf("start test case: %s\n", title) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodPost, "/v1/component_status", strings.NewReader(c.JSON)) + r.ServeHTTP(w, req) + + if title == "positive testcase" { + inc := &v1.Incident{} + err := json.Unmarshal(w.Body.Bytes(), inc) + require.NoError(t, err) + assert.Equal(t, 2, inc.ID) + assert.Equal(t, "Incident", inc.Text) + assert.Equal(t, 0, len(inc.Updates)) //nolint:testifylint + assert.Equal(t, http.StatusOK, c.ExpectedCode) + assert.True(t, strings.HasPrefix(w.Body.String(), c.Expected)) + continue + } + assert.Equal(t, c.ExpectedCode, w.Code) + assert.Equal(t, c.Expected, w.Body.String()) + } +} From 7f7ab2085d08682223e845acee87718b6e686130 Mon Sep 17 00:00:00 2001 From: Ilia Bakhterev Date: Wed, 30 Oct 2024 03:55:10 +0100 Subject: [PATCH 4/9] availability implementation (preview) --- internal/api/routes.go | 1 + internal/api/v1/v1.go | 148 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 11 deletions(-) diff --git a/internal/api/routes.go b/internal/api/routes.go index 994c134..93d3729 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -17,6 +17,7 @@ func (a *API) InitRoutes() { v1Api.POST("component_status", v1.PostComponentStatusHandler(a.db, a.log)) v1Api.GET("incidents", v1.GetIncidentsHandler(a.db, a.log)) + } // setup v2 group routing diff --git a/internal/api/v1/v1.go b/internal/api/v1/v1.go index 53a67b7..54594a8 100644 --- a/internal/api/v1/v1.go +++ b/internal/api/v1/v1.go @@ -120,9 +120,10 @@ func GetIncidentsHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { type Component struct { ComponentID - Attrs []*ComponentAttribute `json:"attributes"` - Name string `json:"name"` - Incidents []*Incident `json:"incidents"` + Attrs []*ComponentAttribute `json:"attributes"` + Name string `json:"name"` + Incidents []*Incident `json:"incidents"` + Availability []*AvailabilityData `json:"availability"` } type ComponentID struct { @@ -134,6 +135,15 @@ type ComponentAttribute struct { Value string `json:"value"` } +type AvailabilityData struct { + MonthlyAvailability []MonthlyAvailability `json:"monthly_availability"` +} + +type MonthlyAvailability struct { + Month string `json:"month"` // Name of the month, "2023-01" + Percentage float64 `json:"percentage"` // Percent (0.0 - 100.0) +} + func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { logger.Debug("retrieve components with incidents") @@ -145,8 +155,28 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { // We can't change this logic, because of date representation. // Will be changed in the V2. - components := make([]*Component, len(r)) - for index, component := range r { + + // needs to uncomment after debugging: + //components := make([]*Component, len(r)) + //for index, component := range r { + // attrs := make([]*ComponentAttribute, len(component.Attrs)) + // for i, attr := range component.Attrs { + // attrs[i] = &ComponentAttribute{ + // Name: attr.Name, + // Value: attr.Value, + // } + // } + // needs to uncomment after debugging <- + + // needs to remove after debugging: + var components []*Component + for _, component := range r { + // Name checking + if component.Name != "Auto Scaling" { + continue // Skip + } + // needs to remove after debugging <- + attrs := make([]*ComponentAttribute, len(component.Attrs)) for i, attr := range component.Attrs { attrs[i] = &ComponentAttribute{ @@ -188,18 +218,114 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { incidents[i] = newInc } - components[index] = &Component{ - ComponentID: ComponentID{int(component.ID)}, - Attrs: attrs, - Name: component.Name, - Incidents: incidents, - } + availability, _ := calculateAvailability(&component) + + components = append(components, &Component{ + ComponentID: ComponentID{int(component.ID)}, + Attrs: attrs, + Name: component.Name, + Incidents: incidents, + Availability: []*AvailabilityData{{MonthlyAvailability: availability}}, + }) + // needs to uncomment after debugging: + //components[index] = &Component{ + // ComponentID: ComponentID{int(component.ID)}, + // Attrs: attrs, + // Name: component.Name, + // Incidents: incidents, + // Availability: []*AvailabilityData{{MonthlyAvailability: availability}}, + //} + // needs to uncomment after debugging <- } c.JSON(http.StatusOK, components) } } +// Function calculates the availability of a component over the last +// year based only on completed incidents and an impact of 3 +func calculateAvailability(component *db.Component) ([]MonthlyAvailability, error) { + // Get the current date and starting point (12 months ago) + currentDate := time.Now() + startDate := currentDate.AddDate(0, -11, 0) // a year ago, including current the month + monthlyDowntime := make(map[string]float64) + for d := startDate; !d.After(currentDate); d = d.AddDate(0, 1, 0) { + month := d.Format("2006-01") + monthlyDowntime[month] = 0 + } + + for _, inc := range component.Incidents { + if inc.EndDate == nil || inc.Impact == nil || *inc.Impact != 3 { + continue + } + + incidentStart := time.Time(*inc.StartDate) + incidentEnd := time.Time(*inc.EndDate) + + if incidentEnd.Before(startDate) || incidentStart.After(currentDate) { + continue + } + + if incidentStart.Before(startDate) { + incidentStart = startDate + } + if incidentEnd.After(currentDate) { + incidentEnd = currentDate + } + + for d := incidentStart; d.Before(incidentEnd); d = d.AddDate(0, 1, 0) { + month := d.Format("2006-01") + monthStart := time.Date(d.Year(), d.Month(), 1, 0, 0, 0, 0, d.Location()) + monthEnd := monthStart.AddDate(0, 1, 0) + + downtimeStart := maxTime(incidentStart, monthStart) + downtimeEnd := minTime(incidentEnd, monthEnd) + downtime := downtimeEnd.Sub(downtimeStart).Hours() + + monthlyDowntime[month] += downtime + } + } + + monthlyAvailability := make([]MonthlyAvailability, 0, len(monthlyDowntime)) + for month, downtime := range monthlyDowntime { + totalHours := hoursInMonth(month) + availability := 100 - (downtime / totalHours * 100) + // rounding the availability value to the nearest hundredth + availability = float64(int(availability*100+0.5)) / 100 + monthlyAvailability = append(monthlyAvailability, MonthlyAvailability{ + Month: month, + Percentage: availability, + }) + } + + return monthlyAvailability, nil +} + +func minTime(start, end time.Time) time.Time { + if start.Before(end) { + return start + } + return end +} + +func maxTime(start, end time.Time) time.Time { + if start.After(end) { + return start + } + return end +} + +func hoursInMonth(month string) float64 { + parsedTime, err := time.Parse("2006-01", month) + if err != nil { + return 0 + } + firstDay := time.Date(parsedTime.Year(), parsedTime.Month(), 1, 0, 0, 0, 0, parsedTime.Location()) + nextMonth := firstDay.AddDate(0, 1, 0) + + return float64(nextMonth.Sub(firstDay).Hours()) +} + type ComponentStatusPost struct { Name string `json:"name" binding:"required"` Impact int `json:"impact" binding:"required,gte=1,lte=3"` From 189cb13bf2958cf45c2df08b912a65be09de460b Mon Sep 17 00:00:00 2001 From: Ilia Bakhterev Date: Wed, 30 Oct 2024 12:00:24 +0100 Subject: [PATCH 5/9] API availability endpoint implementation --- internal/api/routes.go | 6 +- internal/api/v1/v1.go | 148 ++------------------------ internal/api/v2/v2.go | 173 ++++++++++++++++++++++++++++++ internal/api/v2/v2_test.go | 209 +++++++++++++++++++++++++++++++++---- openapi.yaml | 54 ++++++++++ 5 files changed, 427 insertions(+), 163 deletions(-) diff --git a/internal/api/routes.go b/internal/api/routes.go index 93d3729..1a6ae0a 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -17,7 +17,6 @@ func (a *API) InitRoutes() { v1Api.POST("component_status", v1.PostComponentStatusHandler(a.db, a.log)) v1Api.GET("incidents", v1.GetIncidentsHandler(a.db, a.log)) - } // setup v2 group routing @@ -32,12 +31,13 @@ func (a *API) InitRoutes() { v2Api.GET("incidents/:id", v2.GetIncidentHandler(a.db, a.log)) v2Api.PATCH("incidents/:id", a.ValidateComponentsMW(), v2.PatchIncidentHandler(a.db, a.log)) + v2Api.GET("availability", v2.GetComponentsAvailabilityHandler(a.db, a.log)) + //nolint:gocritic //v2Api.GET("rss") //v2Api.GET("history") - //v2Api.GET("availability") + //v2Api.GET("/separate//") - > investigate it!!! - // //v2Api.GET("/login/:name") //v2Api.GET("/auth/:name") //v2Api.GET("/logout") diff --git a/internal/api/v1/v1.go b/internal/api/v1/v1.go index 54594a8..53a67b7 100644 --- a/internal/api/v1/v1.go +++ b/internal/api/v1/v1.go @@ -120,10 +120,9 @@ func GetIncidentsHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { type Component struct { ComponentID - Attrs []*ComponentAttribute `json:"attributes"` - Name string `json:"name"` - Incidents []*Incident `json:"incidents"` - Availability []*AvailabilityData `json:"availability"` + Attrs []*ComponentAttribute `json:"attributes"` + Name string `json:"name"` + Incidents []*Incident `json:"incidents"` } type ComponentID struct { @@ -135,15 +134,6 @@ type ComponentAttribute struct { Value string `json:"value"` } -type AvailabilityData struct { - MonthlyAvailability []MonthlyAvailability `json:"monthly_availability"` -} - -type MonthlyAvailability struct { - Month string `json:"month"` // Name of the month, "2023-01" - Percentage float64 `json:"percentage"` // Percent (0.0 - 100.0) -} - func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { logger.Debug("retrieve components with incidents") @@ -155,28 +145,8 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { // We can't change this logic, because of date representation. // Will be changed in the V2. - - // needs to uncomment after debugging: - //components := make([]*Component, len(r)) - //for index, component := range r { - // attrs := make([]*ComponentAttribute, len(component.Attrs)) - // for i, attr := range component.Attrs { - // attrs[i] = &ComponentAttribute{ - // Name: attr.Name, - // Value: attr.Value, - // } - // } - // needs to uncomment after debugging <- - - // needs to remove after debugging: - var components []*Component - for _, component := range r { - // Name checking - if component.Name != "Auto Scaling" { - continue // Skip - } - // needs to remove after debugging <- - + components := make([]*Component, len(r)) + for index, component := range r { attrs := make([]*ComponentAttribute, len(component.Attrs)) for i, attr := range component.Attrs { attrs[i] = &ComponentAttribute{ @@ -218,114 +188,18 @@ func GetComponentsStatusHandler(db *db.DB, logger *zap.Logger) gin.HandlerFunc { incidents[i] = newInc } - availability, _ := calculateAvailability(&component) - - components = append(components, &Component{ - ComponentID: ComponentID{int(component.ID)}, - Attrs: attrs, - Name: component.Name, - Incidents: incidents, - Availability: []*AvailabilityData{{MonthlyAvailability: availability}}, - }) - // needs to uncomment after debugging: - //components[index] = &Component{ - // ComponentID: ComponentID{int(component.ID)}, - // Attrs: attrs, - // Name: component.Name, - // Incidents: incidents, - // Availability: []*AvailabilityData{{MonthlyAvailability: availability}}, - //} - // needs to uncomment after debugging <- + components[index] = &Component{ + ComponentID: ComponentID{int(component.ID)}, + Attrs: attrs, + Name: component.Name, + Incidents: incidents, + } } c.JSON(http.StatusOK, components) } } -// Function calculates the availability of a component over the last -// year based only on completed incidents and an impact of 3 -func calculateAvailability(component *db.Component) ([]MonthlyAvailability, error) { - // Get the current date and starting point (12 months ago) - currentDate := time.Now() - startDate := currentDate.AddDate(0, -11, 0) // a year ago, including current the month - monthlyDowntime := make(map[string]float64) - for d := startDate; !d.After(currentDate); d = d.AddDate(0, 1, 0) { - month := d.Format("2006-01") - monthlyDowntime[month] = 0 - } - - for _, inc := range component.Incidents { - if inc.EndDate == nil || inc.Impact == nil || *inc.Impact != 3 { - continue - } - - incidentStart := time.Time(*inc.StartDate) - incidentEnd := time.Time(*inc.EndDate) - - if incidentEnd.Before(startDate) || incidentStart.After(currentDate) { - continue - } - - if incidentStart.Before(startDate) { - incidentStart = startDate - } - if incidentEnd.After(currentDate) { - incidentEnd = currentDate - } - - for d := incidentStart; d.Before(incidentEnd); d = d.AddDate(0, 1, 0) { - month := d.Format("2006-01") - monthStart := time.Date(d.Year(), d.Month(), 1, 0, 0, 0, 0, d.Location()) - monthEnd := monthStart.AddDate(0, 1, 0) - - downtimeStart := maxTime(incidentStart, monthStart) - downtimeEnd := minTime(incidentEnd, monthEnd) - downtime := downtimeEnd.Sub(downtimeStart).Hours() - - monthlyDowntime[month] += downtime - } - } - - monthlyAvailability := make([]MonthlyAvailability, 0, len(monthlyDowntime)) - for month, downtime := range monthlyDowntime { - totalHours := hoursInMonth(month) - availability := 100 - (downtime / totalHours * 100) - // rounding the availability value to the nearest hundredth - availability = float64(int(availability*100+0.5)) / 100 - monthlyAvailability = append(monthlyAvailability, MonthlyAvailability{ - Month: month, - Percentage: availability, - }) - } - - return monthlyAvailability, nil -} - -func minTime(start, end time.Time) time.Time { - if start.Before(end) { - return start - } - return end -} - -func maxTime(start, end time.Time) time.Time { - if start.After(end) { - return start - } - return end -} - -func hoursInMonth(month string) float64 { - parsedTime, err := time.Parse("2006-01", month) - if err != nil { - return 0 - } - firstDay := time.Date(parsedTime.Year(), parsedTime.Month(), 1, 0, 0, 0, 0, parsedTime.Location()) - nextMonth := firstDay.AddDate(0, 1, 0) - - return float64(nextMonth.Sub(firstDay).Hours()) -} - type ComponentStatusPost struct { Name string `json:"name" binding:"required"` Impact int `json:"impact" binding:"required,gte=1,lte=3"` diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go index 1869f12..c3312e5 100644 --- a/internal/api/v2/v2.go +++ b/internal/api/v2/v2.go @@ -3,6 +3,7 @@ package v2 import ( "errors" "net/http" + "sort" "time" "github.com/gin-gonic/gin" @@ -245,6 +246,13 @@ type Component struct { Name string `json:"name"` } +type ComponentAvailability struct { + ComponentID + Name string `json:"name"` + Availability []MonthlyAvailability `json:"availability"` + Region string `json:"region"` +} + type ComponentID struct { ID int `json:"id" uri:"id" binding:"required,gte=0"` } @@ -266,6 +274,12 @@ var availableAttrs = map[string]struct{}{ //nolint:gochecknoglobals "category": {}, } +type MonthlyAvailability struct { + Year int `json:"year"` + Month int `json:"month"` // Number of the month (1 - 12) + Percentage float64 `json:"percentage"` // Percent (0 - 100 / example: 95.23478) +} + func GetComponentsHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { logger.Debug("retrieve components") @@ -370,3 +384,162 @@ func checkComponentAttrs(attrs []ComponentAttribute) error { return nil } + +func GetComponentsAvailabilityHandler(dbInst *db.DB, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger.Debug("retrieve availability of components") + + components, err := dbInst.GetComponentsWithIncidents() + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + availability := make([]*ComponentAvailability, len(components)) + for index, comp := range components { + attrs := make([]ComponentAttribute, len(comp.Attrs)) + for i, attr := range comp.Attrs { + attrs[i] = ComponentAttribute{ + Name: attr.Name, + Value: attr.Value, + } + } + regionValue := "" + for _, attr := range attrs { + if attr.Name == "region" { + regionValue = attr.Value + break + } + } + + incidents := make([]*Incident, len(comp.Incidents)) + for i, inc := range comp.Incidents { + + newInc := &Incident{ + IncidentID: IncidentID{int(inc.ID)}, + IncidentData: IncidentData{ + Title: *inc.Text, + Impact: inc.Impact, + StartDate: *inc.StartDate, + EndDate: inc.EndDate, + Updates: nil, + }, + } + + incidents[i] = newInc + } + + compAvailability, err := calculateAvailability(&comp) + if err != nil { + apiErrors.RaiseInternalErr(c, err) + return + } + + sort.Slice(compAvailability, func(i, j int) bool { + if compAvailability[i].Year == compAvailability[j].Year { + return compAvailability[i].Month > compAvailability[j].Month + } + return compAvailability[i].Year > compAvailability[j].Year + }) + + availability[index] = &ComponentAvailability{ + ComponentID: ComponentID{int(comp.ID)}, + Region: regionValue, + Name: comp.Name, + Availability: compAvailability, + } + } + + c.JSON(http.StatusOK, gin.H{"data": availability}) + } +} + +// TODO: add filters for GET request +// TODO: add comments about logic how it's calculated +func calculateAvailability(component *db.Component) ([]MonthlyAvailability, error) { + periodEndDate := time.Now() + // Get the current date and starting point (12 months ago) + periodStartDate := periodEndDate.AddDate(0, -11, 0) // a year ago, including current the month + monthlyDowntime := make([]float64, 12) // 12 months + + for _, inc := range component.Incidents { + if inc.EndDate == nil || *inc.Impact != 3 { + continue + } + + incidentStart := *inc.StartDate + incidentEnd := *inc.EndDate + + // here we skip all incidents that are not correspond to our period + if incidentEnd.Before(periodStartDate) || incidentStart.After(periodEndDate) { + continue + } + + // if the incident started before availability period (as example the incident was started at 01:00 31/12 and finished at 02:00 01/01), + // we cut the beginning to the period start date, and do the same for the period ending + if incidentStart.Before(periodStartDate) { + incidentStart = periodStartDate + } + if incidentEnd.After(periodEndDate) { + incidentEnd = periodEndDate + } + + current := incidentStart + for current.Before(incidentEnd) { + monthStart := time.Date(current.Year(), current.Month(), 1, 0, 0, 0, 0, time.UTC) + monthEnd := monthStart.AddDate(0, 1, 0) + + if monthEnd.Before(periodStartDate) || monthStart.After(periodEndDate) { + break + } + + downtimeStart := maxTime(incidentStart, monthStart) + downtimeEnd := minTime(incidentEnd, monthEnd) + downtime := downtimeEnd.Sub(downtimeStart).Hours() + + monthIndex := int(downtimeStart.Year()-periodStartDate.Year())*12 + int(downtimeStart.Month()-periodStartDate.Month()) + if monthIndex >= 0 && monthIndex < len(monthlyDowntime) { + monthlyDowntime[monthIndex] += downtime + } + + current = monthEnd + } + } + + monthlyAvailability := make([]MonthlyAvailability, 0, 12) + for i := 0; i < 12; i++ { + monthDate := periodStartDate.AddDate(0, i, 0) + totalHours := hoursInMonth(monthDate.Year(), int(monthDate.Month())) + availability := 100 - (monthlyDowntime[i] / totalHours * 100) + availability = float64(int(availability*100000+0.5)) / 100000 + + monthlyAvailability = append(monthlyAvailability, MonthlyAvailability{ + Year: monthDate.Year(), + Month: int(monthDate.Month()), + Percentage: availability, + }) + } + + return monthlyAvailability, nil +} + +func minTime(start, end time.Time) time.Time { + if start.Before(end) { + return start + } + return end +} + +func maxTime(start, end time.Time) time.Time { + if start.After(end) { + return start + } + return end +} + +func hoursInMonth(year int, month int) float64 { + firstDay := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) + nextMonth := firstDay.AddDate(0, 1, 0) + + return float64(nextMonth.Sub(firstDay).Hours()) +} diff --git a/internal/api/v2/v2_test.go b/internal/api/v2/v2_test.go index 439b170..d26051d 100644 --- a/internal/api/v2/v2_test.go +++ b/internal/api/v2/v2_test.go @@ -21,14 +21,15 @@ import ( func TestGetIncidentsHandler(t *testing.T) { r, m := initTests(t) - str := "2024-09-01T11:45:26.371Z" + startDate := "2024-09-01T11:45:26.371Z" + endDate := "2024-09-04T11:45:26.371Z" - testTime, err := time.Parse(time.RFC3339, str) + testTime, err := time.Parse(time.RFC3339, startDate) require.NoError(t, err) - prepareDB(t, m, testTime) + prepareIncident(t, m, testTime) - var response = `{"data":[{"id":1,"title":"Incident title","impact":0,"components":[150],"start_date":"%s","system":false,"updates":[{"id":1,"status":"resolved","text":"Issue solved.","timestamp":"%s"}]}]}` + var response = `{"data":[{"id":1,"title":"Incident title A","impact":0,"components":[150],"start_date":"%s","end_date":"%s","system":false,"updates":[{"id":1,"status":"resolved","text":"Issue solved.","timestamp":"%s"}]},{"id":2,"title":"Incident title B","impact":3,"components":[151],"start_date":"%s","end_date":"%s","system":false,"updates":[{"id":2,"status":"resolved","text":"Issue solved.","timestamp":"%s"}]}]}` w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/v2/incidents", nil) @@ -36,7 +37,7 @@ func TestGetIncidentsHandler(t *testing.T) { r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) - assert.Equal(t, fmt.Sprintf(response, str, str), w.Body.String()) + assert.Equal(t, fmt.Sprintf(response, startDate, endDate, endDate, startDate, endDate, endDate), w.Body.String()) } func TestReturn404Handler(t *testing.T) { @@ -49,6 +50,133 @@ func TestReturn404Handler(t *testing.T) { assert.Equal(t, `{"errMsg":"page not found"}`, w.Body.String()) } +// Test plan: +// 1. API response, what should be there: 200 + valid JSON +// 2. check +func TestGetComponentsAvailabilityHandler(t *testing.T) { + r, m := initTests(t) + // Mocking data for testing + + currentTime := time.Now().UTC() + year, month, _ := currentTime.Date() + + firstDayOfLastMonth := time.Date(year, month-1, 1, 0, 0, 0, 0, time.UTC) + testTime := firstDayOfLastMonth + prepareAvailability(t, m, testTime) + + getYearAndMonth := func(year, month, offset int) (int, int) { + newMonth := month - offset + for newMonth <= 0 { + year -= 1 + newMonth += 12 + } + return year, newMonth + } + + expectedAvailability := "" + for i := 0; i < 12; i++ { + availYear, availMonth := getYearAndMonth(year, int(month), i) + percentage := 100 + // For the second month (current month in test setup), set percentage to 0 + if i == 1 { + percentage = 0 + } + expectedAvailability += fmt.Sprintf(`{"year":%d,"month":%d,"percentage":%d},`, availYear, availMonth, percentage) + } + // Remove trailing comma + expectedAvailability = expectedAvailability[:len(expectedAvailability)-1] + + response := fmt.Sprintf(`{"data":[{"id":151,"name":"Component B","availability":[%s],"region":"B"}]}`, expectedAvailability) + + // Sending GET request to get availability of components + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/v2/availability", nil) + r.ServeHTTP(w, req) + // Checking status code of response and format + assert.Equal(t, 200, w.Code) + assert.Equal(t, response, w.Body.String()) + // unmarshal data to golang struct +} + +func TestCalculateAvailability(t *testing.T) { + type testCase struct { + description string + Component *db.Component + Result []*MonthlyAvailability + } + + impact := 3 + + comp := db.Component{ + ID: 150, + Name: "DataArts", + Incidents: []*db.Incident{}, + } + + compForSept := comp + stDate := time.Date(2024, 9, 21, 0, 0, 0, 0, time.UTC) + endDate := time.Date(2024, 10, 2, 20, 0, 0, 0, time.UTC) + compForSept.Incidents = append(compForSept.Incidents, &db.Incident{ + ID: 1, + StartDate: &stDate, + EndDate: &endDate, + Impact: &impact, + }) + + testCases := []testCase{ + { + description: "Test case: September (66.66667%)- October (94.08602%)", + Component: &compForSept, + Result: func() []*MonthlyAvailability { + results := make([]*MonthlyAvailability, 12) + + for i := 0; i < 12; i++ { + year, month := getYearAndMonth(time.Now().Year(), int(time.Now().Month()), 12-i-1) + results[i] = &MonthlyAvailability{ + Year: year, + Month: month, + Percentage: 100, + } + if month == 9 { + results[i] = &MonthlyAvailability{ + Month: month, + Percentage: 66.66667, + } + } + if month == 10 { + results[i] = &MonthlyAvailability{ + Month: month, + Percentage: 94.08602, + } + } + } + return results + }(), + }, + } + + for _, tc := range testCases { + result, err := calculateAvailability(tc.Component) + require.NoError(t, err) + + t.Logf("Test '%s': Calculated availability: %+v", tc.description, result) + + assert.Len(t, result, 12) + for i, r := range result { + assert.Equal(t, tc.Result[i].Percentage, r.Percentage) + } + } +} + +func getYearAndMonth(year, month, offset int) (int, int) { + newMonth := month - offset + for newMonth <= 0 { + year-- + newMonth += 12 + } + return year, newMonth +} + func initTests(t *testing.T) (*gin.Engine, sqlmock.Sqlmock) { t.Helper() @@ -80,42 +208,77 @@ func initRoutes(t *testing.T, c *gin.Engine, dbInst *db.DB, log *zap.Logger) { v2Api.POST("incidents", PostIncidentHandler(dbInst, log)) v2Api.GET("incidents/:id", GetIncidentHandler(dbInst, log)) v2Api.PATCH("incidents/:id", PatchIncidentHandler(dbInst, log)) + + v2Api.GET("availability", GetComponentsAvailabilityHandler(dbInst, log)) } } -func prepareDB(t *testing.T, mock sqlmock.Sqlmock, testTime time.Time) { +func prepareIncident(t *testing.T, mock sqlmock.Sqlmock, testTime time.Time) { t.Helper() - rows := sqlmock.NewRows([]string{"id", "text", "start_date", "end_date", "impact", "system"}). - AddRow(1, "Incident title", testTime, nil, 0, false) - mock.ExpectQuery("^SELECT (.+) FROM \"incident\"$").WillReturnRows(rows) + rowsInc := sqlmock.NewRows([]string{"id", "text", "start_date", "end_date", "impact", "system"}). + AddRow(1, "Incident title A", testTime, testTime.Add(time.Hour*72), 0, false). + AddRow(2, "Incident title B", testTime, testTime.Add(time.Hour*72), 3, false) + mock.ExpectQuery("^SELECT (.+) FROM \"incident\"$").WillReturnRows(rowsInc) rowsIncComp := sqlmock.NewRows([]string{"incident_id", "component_id"}). - AddRow(1, 150) + AddRow(1, 150). + AddRow(2, 151) mock.ExpectQuery("^SELECT (.+) FROM \"incident_component_relation\"(.+)").WillReturnRows(rowsIncComp) rowsComp := sqlmock.NewRows([]string{"id", "name"}). - AddRow(150, "Cloud Container Engine") + AddRow(150, "Component A"). + AddRow(151, "Component B") mock.ExpectQuery("^SELECT (.+) FROM \"component\"(.+)").WillReturnRows(rowsComp) rowsStatus := sqlmock.NewRows([]string{"id", "incident_id", "timestamp", "text", "status"}). - AddRow(1, 1, testTime, "Issue solved.", "resolved") + AddRow(1, 1, testTime.Add(time.Hour*72), "Issue solved.", "resolved"). + AddRow(2, 2, testTime.Add(time.Hour*72), "Issue solved.", "resolved") mock.ExpectQuery("^SELECT (.+) FROM \"incident_status\"").WillReturnRows(rowsStatus) rowsCompAttr := sqlmock.NewRows([]string{"id", "component_id", "name", "value"}). AddRows([][]driver.Value{ - { - 859, 150, "category", "Container", - }, - { - 860, 150, "region", "EU-DE", - }, - { - 861, 150, "type", "cce", - }, - }..., - ) + {859, 150, "category", "A"}, + {860, 150, "region", "A"}, + {861, 150, "type", "b"}, + {862, 151, "category", "B"}, + {863, 151, "region", "B"}, + {864, 151, "type", "a"}, + }...) mock.ExpectQuery("^SELECT (.+) FROM \"component_attribute\"").WillReturnRows(rowsCompAttr) mock.NewRowsWithColumnDefinition() } + +func prepareAvailability(t *testing.T, mock sqlmock.Sqlmock, testTime time.Time) { + t.Helper() + + rowsComp := sqlmock.NewRows([]string{"id", "name"}). + AddRow(151, "Component B") + mock.ExpectQuery("^SELECT (.+) FROM \"component\"$").WillReturnRows(rowsComp) + + rowsCompAttr := sqlmock.NewRows([]string{"id", "component_id", "name", "value"}). + AddRows([][]driver.Value{ + {862, 151, "category", "B"}, + {863, 151, "region", "B"}, + {864, 151, "type", "a"}, + }...) + mock.ExpectQuery("^SELECT (.+) FROM \"component_attribute\"").WillReturnRows(rowsCompAttr) + + rowsIncComp := sqlmock.NewRows([]string{"incident_id", "component_id"}). + AddRow(2, 151) + mock.ExpectQuery("^SELECT (.+) FROM \"incident_component_relation\"(.+)").WillReturnRows(rowsIncComp) + + startOfMonth := time.Date(testTime.Year(), testTime.Month(), 1, 0, 0, 0, 0, time.UTC) + startOfNextMonth := startOfMonth.AddDate(0, 1, 0) + + rowsInc := sqlmock.NewRows([]string{"id", "text", "start_date", "end_date", "impact", "system"}). + AddRow(2, "Incident title B", startOfMonth, startOfNextMonth, 3, false) + mock.ExpectQuery("^SELECT (.+) FROM \"incident\" WHERE \"incident\".\"id\" = \\$1$").WillReturnRows(rowsInc) + + rowsStatus := sqlmock.NewRows([]string{"id", "incident_id", "timestamp", "text", "status"}). + AddRow(2, 2, testTime.Add(time.Hour*72), "Issue solved.", "resolved") + mock.ExpectQuery("^SELECT (.+) FROM \"incident_status\"").WillReturnRows(rowsStatus) + + mock.NewRowsWithColumnDefinition() +} diff --git a/openapi.yaml b/openapi.yaml index 78f3f5e..73897de 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -90,6 +90,23 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerError' + /v2/availability: + get: + summary: Get availability. + tags: + - availability + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ComponentAvailability' /v2/incidents: get: summary: Get all incidents. @@ -254,6 +271,43 @@ components: errMsg: type: string example: internal server error + ComponentAvailability: + type: object + required: + - id + - name + - region + - availability + properties: + id: + type: integer + format: int64 + example: 218 + name: + type: string + example: "Auto Scaling" + region: + type: string + example: "EU-DE" + availability: + type: array + items: + type: object + required: + - year + - month + - percentage + properties: + year: + type: integer + example: 2024 + month: + type: integer + example: 5 + percentage: + type: number + format: float + example: 99.999666 Incidents: type: object properties: From bab637175d7304bdd263973a8e411185e969007f Mon Sep 17 00:00:00 2001 From: Ilia Bakhterev Date: Thu, 28 Nov 2024 17:33:09 +0100 Subject: [PATCH 6/9] dependencies updated --- go.mod | 63 ------------------------------------------- go.sum | 9 ------- internal/db/models.go | 19 ------------- 3 files changed, 91 deletions(-) diff --git a/go.mod b/go.mod index 84dc2e4..6c9052f 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,6 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 - github.com/lib/pq v1.10.9 - github.com/ory/dockertest/v3 v3.11.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.34.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 @@ -25,11 +23,6 @@ require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - dario.cat/mergo v1.0.1 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/bytedance/sonic v1.12.1 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -38,40 +31,23 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.3.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/docker v27.3.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect @@ -80,16 +56,11 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect @@ -97,40 +68,21 @@ require ( github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.3.0 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/shirou/gopsutil/v3 v3.23.12 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/testcontainers/testcontainers-go v0.34.0 // indirect - github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -140,14 +92,6 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.9.0 // indirect golang.org/x/crypto v0.28.0 // indirect @@ -157,12 +101,5 @@ require ( golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/protobuf v1.35.1 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8dc98e7..1929a56 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -113,10 +111,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -176,7 +170,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -303,8 +296,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/db/models.go b/internal/db/models.go index 71a1fd1..523477c 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -10,10 +10,6 @@ type Component struct { Name string `json:"name,omitempty"` Attrs []ComponentAttr `json:"attributes,omitempty"` Incidents []*Incident `json:"incidents,omitempty" gorm:"many2many:incident_component_relation"` - ID uint `json:"id"` - Name string `json:"name,omitempty"` - Attrs []ComponentAttr `json:"attributes,omitempty"` - Incidents []*Incident `json:"incidents,omitempty" gorm:"many2many:incident_component_relation"` } func (c *Component) TableName() string { @@ -35,21 +31,6 @@ func (c *Component) PrintAttrs() string { return fmt.Sprintf("%s (%s, %s, %s)", c.Name, category, region, compType) } -func (c *Component) PrintAttrs() string { - var category, region, compType string - for _, a := range c.Attrs { - switch a.Name { - case "category": - category = a.Value - case "region": - region = a.Value - case "type": - compType = a.Value - } - } - return fmt.Sprintf("%s (%s, %s, %s)", c.Name, category, region, compType) -} - type ComponentAttr struct { ID uint `json:"-"` ComponentID uint `json:"-"` From f7e7adb344688e2395c122c0d8c3e39d0d8f7414 Mon Sep 17 00:00:00 2001 From: Ilia Bakhterev Date: Fri, 29 Nov 2024 18:14:25 +0100 Subject: [PATCH 7/9] fix: (golangci-lint) --- internal/api/v1/v1_test.go | 2 +- internal/api/v2/v2.go | 105 ++++++++++++++++++++++++------------- internal/api/v2/v2_test.go | 13 ++--- 3 files changed, 75 insertions(+), 45 deletions(-) diff --git a/internal/api/v1/v1_test.go b/internal/api/v1/v1_test.go index becb8f8..c96f1f5 100644 --- a/internal/api/v1/v1_test.go +++ b/internal/api/v1/v1_test.go @@ -22,7 +22,7 @@ func TestCustomTimeFormat(t *testing.T) { data, err := json.Marshal(inc) require.NoError(t, err) - assert.Equal(t, "{\"id\":0,\"text\":\"\",\"impact\":null,\"start_date\":\"2024-09-01 11:45\",\"end_date\":null,\"updates\":null}", string(data)) + assert.JSONEq(t, "{\"id\":0,\"text\":\"\",\"impact\":null,\"start_date\":\"2024-09-01 11:45\",\"end_date\":null,\"updates\":null}", string(data)) inc = &Incident{} err = json.Unmarshal(data, &inc) diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go index c3312e5..8292c86 100644 --- a/internal/api/v2/v2.go +++ b/internal/api/v2/v2.go @@ -2,6 +2,7 @@ package v2 import ( "errors" + "fmt" "net/http" "sort" "time" @@ -414,7 +415,6 @@ func GetComponentsAvailabilityHandler(dbInst *db.DB, logger *zap.Logger) gin.Han incidents := make([]*Incident, len(comp.Incidents)) for i, inc := range comp.Incidents { - newInc := &Incident{ IncidentID: IncidentID{int(inc.ID)}, IncidentData: IncidentData{ @@ -425,22 +425,22 @@ func GetComponentsAvailabilityHandler(dbInst *db.DB, logger *zap.Logger) gin.Han Updates: nil, }, } - incidents[i] = newInc } - compAvailability, err := calculateAvailability(&comp) - if err != nil { - apiErrors.RaiseInternalErr(c, err) + compAvailability, calcErr := calculateAvailability(&comp) + if calcErr != nil { + apiErrors.RaiseInternalErr(c, calcErr) return } - sort.Slice(compAvailability, func(i, j int) bool { - if compAvailability[i].Year == compAvailability[j].Year { - return compAvailability[i].Month > compAvailability[j].Month - } - return compAvailability[i].Year > compAvailability[j].Year - }) + sortComponentAvailability(compAvailability) + // sort.Slice(compAvailability, func(i, j int) bool { + // if compAvailability[i].Year == compAvailability[j].Year { + // return compAvailability[i].Month > compAvailability[j].Month + // } + // return compAvailability[i].Year > compAvailability[j].Year + // }) availability[index] = &ComponentAvailability{ ComponentID: ComponentID{int(comp.ID)}, @@ -454,34 +454,56 @@ func GetComponentsAvailabilityHandler(dbInst *db.DB, logger *zap.Logger) gin.Han } } +func sortComponentAvailability(availabilities []MonthlyAvailability) { + sort.Slice(availabilities, func(i, j int) bool { + if availabilities[i].Year == availabilities[j].Year { + return availabilities[i].Month > availabilities[j].Month + } + return availabilities[i].Year > availabilities[j].Year + }) +} + // TODO: add filters for GET request -// TODO: add comments about logic how it's calculated func calculateAvailability(component *db.Component) ([]MonthlyAvailability, error) { + const ( + monthsInYear = 12 + precisionFactor = 100000 + fullPercentage = 100 + availabilityMonths = 11 + roundFactor = 0.5 + ) + + if component == nil { + return nil, fmt.Errorf("component is nil") + } + + if len(component.Incidents) == 0 { + return nil, nil + } + periodEndDate := time.Now() // Get the current date and starting point (12 months ago) - periodStartDate := periodEndDate.AddDate(0, -11, 0) // a year ago, including current the month - monthlyDowntime := make([]float64, 12) // 12 months + periodStartDate := periodEndDate.AddDate(0, -availabilityMonths, 0) // a year ago, including current the month + monthlyDowntime := make([]float64, monthsInYear) // 12 months for _, inc := range component.Incidents { if inc.EndDate == nil || *inc.Impact != 3 { continue } - incidentStart := *inc.StartDate - incidentEnd := *inc.EndDate - // here we skip all incidents that are not correspond to our period - if incidentEnd.Before(periodStartDate) || incidentStart.After(periodEndDate) { - continue - } - - // if the incident started before availability period (as example the incident was started at 01:00 31/12 and finished at 02:00 01/01), + // if the incident started before availability period + // (as example the incident was started at 01:00 31/12 and finished at 02:00 01/01), // we cut the beginning to the period start date, and do the same for the period ending - if incidentStart.Before(periodStartDate) { - incidentStart = periodStartDate - } - if incidentEnd.After(periodEndDate) { - incidentEnd = periodEndDate + + incidentStart, incidentEnd, valid := adjustIncidentPeriod( + *inc.StartDate, + *inc.EndDate, + periodStartDate, + periodEndDate, + ) + if !valid { + continue } current := incidentStart @@ -489,15 +511,12 @@ func calculateAvailability(component *db.Component) ([]MonthlyAvailability, erro monthStart := time.Date(current.Year(), current.Month(), 1, 0, 0, 0, 0, time.UTC) monthEnd := monthStart.AddDate(0, 1, 0) - if monthEnd.Before(periodStartDate) || monthStart.After(periodEndDate) { - break - } - downtimeStart := maxTime(incidentStart, monthStart) downtimeEnd := minTime(incidentEnd, monthEnd) downtime := downtimeEnd.Sub(downtimeStart).Hours() - monthIndex := int(downtimeStart.Year()-periodStartDate.Year())*12 + int(downtimeStart.Month()-periodStartDate.Month()) + monthIndex := (downtimeStart.Year()-periodStartDate.Year())*monthsInYear + + int(downtimeStart.Month()-periodStartDate.Month()) if monthIndex >= 0 && monthIndex < len(monthlyDowntime) { monthlyDowntime[monthIndex] += downtime } @@ -506,12 +525,12 @@ func calculateAvailability(component *db.Component) ([]MonthlyAvailability, erro } } - monthlyAvailability := make([]MonthlyAvailability, 0, 12) - for i := 0; i < 12; i++ { + monthlyAvailability := make([]MonthlyAvailability, 0, monthsInYear) + for i := range [monthsInYear]int{} { monthDate := periodStartDate.AddDate(0, i, 0) totalHours := hoursInMonth(monthDate.Year(), int(monthDate.Month())) - availability := 100 - (monthlyDowntime[i] / totalHours * 100) - availability = float64(int(availability*100000+0.5)) / 100000 + availability := fullPercentage - (monthlyDowntime[i] / totalHours * fullPercentage) + availability = float64(int(availability*precisionFactor+roundFactor)) / precisionFactor monthlyAvailability = append(monthlyAvailability, MonthlyAvailability{ Year: monthDate.Year(), @@ -523,6 +542,20 @@ func calculateAvailability(component *db.Component) ([]MonthlyAvailability, erro return monthlyAvailability, nil } +func adjustIncidentPeriod(incidentStart, incidentEnd, periodStart, periodEnd time.Time) (time.Time, time.Time, bool) { + + if incidentEnd.Before(periodStart) || incidentStart.After(periodEnd) { + return time.Time{}, time.Time{}, false + } + if incidentStart.Before(periodStart) { + incidentStart = periodStart + } + if incidentEnd.After(periodEnd) { + incidentEnd = periodEnd + } + return incidentStart, incidentEnd, true +} + func minTime(start, end time.Time) time.Time { if start.Before(end) { return start diff --git a/internal/api/v2/v2_test.go b/internal/api/v2/v2_test.go index d26051d..7884739 100644 --- a/internal/api/v2/v2_test.go +++ b/internal/api/v2/v2_test.go @@ -47,12 +47,9 @@ func TestReturn404Handler(t *testing.T) { r.ServeHTTP(w, req) assert.Equal(t, 404, w.Code) - assert.Equal(t, `{"errMsg":"page not found"}`, w.Body.String()) + assert.JSONEq(t, `{"errMsg":"page not found"}`, w.Body.String()) } -// Test plan: -// 1. API response, what should be there: 200 + valid JSON -// 2. check func TestGetComponentsAvailabilityHandler(t *testing.T) { r, m := initTests(t) // Mocking data for testing @@ -67,14 +64,14 @@ func TestGetComponentsAvailabilityHandler(t *testing.T) { getYearAndMonth := func(year, month, offset int) (int, int) { newMonth := month - offset for newMonth <= 0 { - year -= 1 + year-- newMonth += 12 } return year, newMonth } expectedAvailability := "" - for i := 0; i < 12; i++ { + for i := range [12]int{} { availYear, availMonth := getYearAndMonth(year, int(month), i) percentage := 100 // For the second month (current month in test setup), set percentage to 0 @@ -130,7 +127,7 @@ func TestCalculateAvailability(t *testing.T) { Result: func() []*MonthlyAvailability { results := make([]*MonthlyAvailability, 12) - for i := 0; i < 12; i++ { + for i := range [12]int{} { year, month := getYearAndMonth(time.Now().Year(), int(time.Now().Month()), 12-i-1) results[i] = &MonthlyAvailability{ Year: year, @@ -163,7 +160,7 @@ func TestCalculateAvailability(t *testing.T) { assert.Len(t, result, 12) for i, r := range result { - assert.Equal(t, tc.Result[i].Percentage, r.Percentage) + assert.InEpsilon(t, tc.Result[i].Percentage, r.Percentage, 0.0001) } } } From 7ff93a4ddbda3045975cb096b187d022cb70b31b Mon Sep 17 00:00:00 2001 From: Ilia Bakhterev Date: Fri, 29 Nov 2024 18:19:45 +0100 Subject: [PATCH 8/9] unnecessary leading newline removed --- internal/api/v2/v2.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/v2/v2.go b/internal/api/v2/v2.go index 8292c86..bcf43c7 100644 --- a/internal/api/v2/v2.go +++ b/internal/api/v2/v2.go @@ -543,7 +543,6 @@ func calculateAvailability(component *db.Component) ([]MonthlyAvailability, erro } func adjustIncidentPeriod(incidentStart, incidentEnd, periodStart, periodEnd time.Time) (time.Time, time.Time, bool) { - if incidentEnd.Before(periodStart) || incidentStart.After(periodEnd) { return time.Time{}, time.Time{}, false } From 7dc258d7ba8470be975056d9a543a7b11c95a9e4 Mon Sep 17 00:00:00 2001 From: Sergei Martynov Date: Mon, 9 Dec 2024 09:17:15 +0100 Subject: [PATCH 9/9] remove unused dump --- tests/testdata/dumb_test.sql | 416 ----------------------------------- 1 file changed, 416 deletions(-) delete mode 100644 tests/testdata/dumb_test.sql diff --git a/tests/testdata/dumb_test.sql b/tests/testdata/dumb_test.sql deleted file mode 100644 index 13a1610..0000000 --- a/tests/testdata/dumb_test.sql +++ /dev/null @@ -1,416 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 15.8 (Debian 15.8-1.pgdg120+1) --- Dumped by pg_dump version 16.4 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: incidentimpactenum; Type: TYPE; Schema: public; Owner: pg --- - -CREATE TYPE public.incidentimpactenum AS ENUM ( - 'maintenance', - 'minor', - 'major', - 'outage' -); - - -ALTER TYPE public.incidentimpactenum OWNER TO pg; - -SET default_tablespace = ''; - -SET default_table_access_method = heap; - --- --- Name: component; Type: TABLE; Schema: public; Owner: pg --- - -CREATE TABLE public.component ( - id integer NOT NULL, - name character varying NOT NULL -); - - -ALTER TABLE public.component OWNER TO pg; - --- --- Name: component_attribute; Type: TABLE; Schema: public; Owner: pg --- - -CREATE TABLE public.component_attribute ( - id integer NOT NULL, - component_id integer, - name character varying NOT NULL, - value character varying NOT NULL -); - - -ALTER TABLE public.component_attribute OWNER TO pg; - --- --- Name: component_attribute_id_seq; Type: SEQUENCE; Schema: public; Owner: pg --- - -CREATE SEQUENCE public.component_attribute_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.component_attribute_id_seq OWNER TO pg; - --- --- Name: component_attribute_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg --- - -ALTER SEQUENCE public.component_attribute_id_seq OWNED BY public.component_attribute.id; - - --- --- Name: component_id_seq; Type: SEQUENCE; Schema: public; Owner: pg --- - -CREATE SEQUENCE public.component_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.component_id_seq OWNER TO pg; - --- --- Name: component_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg --- - -ALTER SEQUENCE public.component_id_seq OWNED BY public.component.id; - - --- --- Name: incident; Type: TABLE; Schema: public; Owner: pg --- - -CREATE TABLE public.incident ( - id integer NOT NULL, - text character varying NOT NULL, - start_date timestamp without time zone NOT NULL, - end_date timestamp without time zone, - impact smallint NOT NULL, - system boolean DEFAULT false NOT NULL -); - - -ALTER TABLE public.incident OWNER TO pg; - --- --- Name: incident_component_relation; Type: TABLE; Schema: public; Owner: pg --- - -CREATE TABLE public.incident_component_relation ( - incident_id integer NOT NULL, - component_id integer NOT NULL -); - - -ALTER TABLE public.incident_component_relation OWNER TO pg; - --- --- Name: incident_id_seq; Type: SEQUENCE; Schema: public; Owner: pg --- - -CREATE SEQUENCE public.incident_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.incident_id_seq OWNER TO pg; - --- --- Name: incident_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg --- - -ALTER SEQUENCE public.incident_id_seq OWNED BY public.incident.id; - - --- --- Name: incident_status; Type: TABLE; Schema: public; Owner: pg --- - -CREATE TABLE public.incident_status ( - id integer NOT NULL, - incident_id integer, - "timestamp" timestamp without time zone NOT NULL, - text character varying NOT NULL, - status character varying NOT NULL -); - - -ALTER TABLE public.incident_status OWNER TO pg; - --- --- Name: incident_status_id_seq; Type: SEQUENCE; Schema: public; Owner: pg --- - -CREATE SEQUENCE public.incident_status_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.incident_status_id_seq OWNER TO pg; - --- --- Name: incident_status_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pg --- - -ALTER SEQUENCE public.incident_status_id_seq OWNED BY public.incident_status.id; - - --- --- Name: component id; Type: DEFAULT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.component ALTER COLUMN id SET DEFAULT nextval('public.component_id_seq'::regclass); - - --- --- Name: component_attribute id; Type: DEFAULT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.component_attribute ALTER COLUMN id SET DEFAULT nextval('public.component_attribute_id_seq'::regclass); - - --- --- Name: incident id; Type: DEFAULT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.incident ALTER COLUMN id SET DEFAULT nextval('public.incident_id_seq'::regclass); - - --- --- Name: incident_status id; Type: DEFAULT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.incident_status ALTER COLUMN id SET DEFAULT nextval('public.incident_status_id_seq'::regclass); - - --- --- Data for Name: component; Type: TABLE DATA; Schema: public; Owner: pg --- - -COPY public.component (id, name) FROM stdin; -1 Cloud Container Engine -2 Cloud Container Engine -3 Elastic Cloud Server -4 Elastic Cloud Server -5 Distributed Cache Service -6 Distributed Cache Service -\. - - --- --- Data for Name: component_attribute; Type: TABLE DATA; Schema: public; Owner: pg --- - -COPY public.component_attribute (id, component_id, name, value) FROM stdin; -1 1 region EU-DE -2 1 category Container -3 1 type cce -4 2 region EU-NL -5 2 category Container -6 2 type cce -7 3 region EU-DE -8 3 category Compute -9 3 type ecs -10 4 region EU-NL -11 4 category Compute -12 4 type ecs -13 5 region EU-DE -14 5 category Database -15 5 type dcs -16 6 region EU-NL -17 6 category Database -18 6 type dcs -\. - - --- --- Data for Name: incident; Type: TABLE DATA; Schema: public; Owner: pg --- - -COPY public.incident (id, text, start_date, end_date, impact, system) FROM stdin; -1 Opened incident without any update 2024-10-24 10:12:42 \N 1 f -\. - - --- --- Data for Name: incident_component_relation; Type: TABLE DATA; Schema: public; Owner: pg --- - -COPY public.incident_component_relation (incident_id, component_id) FROM stdin; -1 1 -\. - - --- --- Data for Name: incident_status; Type: TABLE DATA; Schema: public; Owner: pg --- - -COPY public.incident_status (id, incident_id, "timestamp", text, status) FROM stdin; -\. - - --- --- Name: component_attribute_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg --- - -SELECT pg_catalog.setval('public.component_attribute_id_seq', 12, true); - - --- --- Name: component_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg --- - -SELECT pg_catalog.setval('public.component_id_seq', 4, true); - - --- --- Name: incident_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg --- - -SELECT pg_catalog.setval('public.incident_id_seq', 1, true); - - --- --- Name: incident_status_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pg --- - -SELECT pg_catalog.setval('public.incident_status_id_seq', 1, false); - - --- --- Name: component_attribute component_attribute_pkey; Type: CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.component_attribute - ADD CONSTRAINT component_attribute_pkey PRIMARY KEY (id); - - --- --- Name: component component_pkey; Type: CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.component - ADD CONSTRAINT component_pkey PRIMARY KEY (id); - - --- --- Name: incident incident_pkey; Type: CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.incident - ADD CONSTRAINT incident_pkey PRIMARY KEY (id); - - --- --- Name: incident_status incident_status_pkey; Type: CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.incident_status - ADD CONSTRAINT incident_status_pkey PRIMARY KEY (id); - - --- --- Name: inc_comp_rel; Type: INDEX; Schema: public; Owner: pg --- - -CREATE UNIQUE INDEX inc_comp_rel ON public.incident_component_relation USING btree (incident_id, component_id); - - --- --- Name: ix_component_attribute_component_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_component_attribute_component_id ON public.component_attribute USING btree (component_id); - - --- --- Name: ix_component_attribute_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_component_attribute_id ON public.component_attribute USING btree (id); - - --- --- Name: ix_component_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_component_id ON public.component USING btree (id); - - --- --- Name: ix_incident_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_incident_id ON public.incident USING btree (id); - - --- --- Name: ix_incident_status_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_incident_status_id ON public.incident_status USING btree (id); - - --- --- Name: ix_incident_status_incident_id; Type: INDEX; Schema: public; Owner: pg --- - -CREATE INDEX ix_incident_status_incident_id ON public.incident_status USING btree (incident_id); - - --- --- Name: component_attribute component_attribute_component_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.component_attribute - ADD CONSTRAINT component_attribute_component_id_fkey FOREIGN KEY (component_id) REFERENCES public.component(id); - - --- --- Name: incident_component_relation incident_component_relation_component_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: pg --- - -ALTER TABLE ONLY public.incident_component_relation - ADD CONSTRAINT incident_component_relation_component_id_fkey FOREIGN KEY (component_id) REFERENCES public.component(id); - - --- --- PostgreSQL database dump complete --- -