diff --git a/cmd/metal-api/internal/datastore/event.go b/cmd/metal-api/internal/datastore/event.go index 6b1a1dc65..eeaa7a031 100644 --- a/cmd/metal-api/internal/datastore/event.go +++ b/cmd/metal-api/internal/datastore/event.go @@ -1,7 +1,11 @@ package datastore import ( + "fmt" + "time" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" + "github.com/metal-stack/metal-lib/zapup" ) // ListProvisioningEventContainers returns all machine provisioning event containers. @@ -35,3 +39,67 @@ func (rs *RethinkStore) CreateProvisioningEventContainer(ec *metal.ProvisioningE func (rs *RethinkStore) UpsertProvisioningEventContainer(ec *metal.ProvisioningEventContainer) error { return rs.upsertEntity(rs.eventTable(), ec) } + +func (rs *RethinkStore) EvaluateMachineLiveliness(m metal.Machine) (metal.MachineLiveliness, error) { + provisioningEvents, err := rs.FindProvisioningEventContainer(m.ID) + if err != nil { + // we have no provisioning events... we cannot tell + return metal.MachineLivelinessUnknown, fmt.Errorf("no provisioningEvents found for ID: %s", m.ID) + } + + old := *provisioningEvents + + if provisioningEvents.LastEventTime != nil { + if time.Since(*provisioningEvents.LastEventTime) > metal.MachineDeadAfter { + if m.Allocation != nil { + // the machine is either dead or the customer did turn off the phone home service + provisioningEvents.Liveliness = metal.MachineLivelinessUnknown + } else { + // the machine is just dead + provisioningEvents.Liveliness = metal.MachineLivelinessDead + } + } else { + provisioningEvents.Liveliness = metal.MachineLivelinessAlive + } + err = rs.UpdateProvisioningEventContainer(&old, provisioningEvents) + if err != nil { + return provisioningEvents.Liveliness, err + } + } + + return provisioningEvents.Liveliness, nil +} + +func (rs *RethinkStore) ProvisioningEventForMachine(machineID string, event metal.ProvisioningEvent) (*metal.ProvisioningEventContainer, error) { + ec, err := rs.FindProvisioningEventContainer(machineID) + if err != nil && !metal.IsNotFound(err) { + return nil, err + } + + if ec == nil { + ec = &metal.ProvisioningEventContainer{ + Base: metal.Base{ + ID: machineID, + }, + Liveliness: metal.MachineLivelinessAlive, + } + } + now := time.Now() + ec.LastEventTime = &now + + if event.Event == metal.ProvisioningEventAlive { + zapup.MustRootLogger().Sugar().Debugw("received provisioning alive event", "id", ec.ID) + ec.Liveliness = metal.MachineLivelinessAlive + } else if event.Event == metal.ProvisioningEventPhonedHome && len(ec.Events) > 0 && ec.Events[0].Event == metal.ProvisioningEventPhonedHome { + zapup.MustRootLogger().Sugar().Debugw("swallowing repeated phone home event", "id", ec.ID) + ec.Liveliness = metal.MachineLivelinessAlive + } else { + ec.Events = append([]metal.ProvisioningEvent{event}, ec.Events...) + ec.IncompleteProvisioningCycles = ec.CalculateIncompleteCycles(zapup.MustRootLogger().Sugar()) + ec.Liveliness = metal.MachineLivelinessAlive + } + ec.TrimEvents(metal.ProvisioningEventsInspectionLimit) + + err = rs.UpsertProvisioningEventContainer(ec) + return ec, err +} diff --git a/cmd/metal-api/internal/service/event-service.go b/cmd/metal-api/internal/service/event-service.go new file mode 100644 index 000000000..910efd5cc --- /dev/null +++ b/cmd/metal-api/internal/service/event-service.go @@ -0,0 +1,152 @@ +package service + +import ( + "errors" + "net/http" + "time" + + "github.com/metal-stack/security" + + "github.com/metal-stack/metal-lib/httperrors" + "github.com/metal-stack/metal-lib/zapup" + "go.uber.org/zap" + + mdm "github.com/metal-stack/masterdata-api/pkg/client" + + "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" + v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/utils" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +type eventResource struct { + webResource + mdc mdm.Client + userGetter security.UserGetter +} + +// NewMachine returns a webservice for machine specific endpoints. +func NewEvent( + ds *datastore.RethinkStore, + mdc mdm.Client, + userGetter security.UserGetter, +) (*restful.WebService, error) { + r := eventResource{ + webResource: webResource{ + ds: ds, + }, + mdc: mdc, + userGetter: userGetter, + } + + return r.webService(), nil +} + +// webService creates the webservice endpoint +func (r eventResource) webService() *restful.WebService { + ws := new(restful.WebService) + ws. + Path(BasePath + "v1/event"). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + + tags := []string{"event"} + + ws.Route(ws.GET("/machine/{id}"). + To(viewer(r.getProvisioningEventContainer)). + Operation("getProvisioningEventContainer"). + Doc("get the current machine provisioning event container"). + Param(ws.PathParameter("id", "identifier of the machine").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(v1.MachineRecentProvisioningEvents{}). + Returns(http.StatusOK, "OK", v1.MachineRecentProvisioningEvents{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.POST("/machine/{id}"). + To(editor(r.addProvisioningEvent)). + Operation("addProvisioningEvent"). + Doc("adds a machine provisioning event"). + Param(ws.PathParameter("id", "identifier of the machine").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(v1.MachineProvisioningEvent{}). + Writes(v1.MachineRecentProvisioningEvents{}). + Returns(http.StatusOK, "OK", v1.MachineRecentProvisioningEvents{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + return ws +} + +func (r eventResource) getProvisioningEventContainer(request *restful.Request, response *restful.Response) { + id := request.PathParameter("id") + + // check for existence of the machine + _, err := r.ds.FindMachineByID(id) + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + + ec, err := r.ds.FindProvisioningEventContainer(id) + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + err = response.WriteHeaderAndEntity(http.StatusOK, v1.NewMachineRecentProvisioningEvents(ec)) + if err != nil { + zapup.MustRootLogger().Error("Failed to send response", zap.Error(err)) + return + } +} + +func (r eventResource) addProvisioningEvent(request *restful.Request, response *restful.Response) { + id := request.PathParameter("id") + m, err := r.ds.FindMachineByID(id) + if err != nil && !metal.IsNotFound(err) { + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + } + + // an event can actually create an empty machine. This enables us to also catch the very first PXE Booting event + // in a machine lifecycle + if m == nil { + m = &metal.Machine{ + Base: metal.Base{ + ID: id, + }, + } + err = r.ds.CreateMachine(m) + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + } + + var requestPayload v1.MachineProvisioningEvent + err = request.ReadEntity(&requestPayload) + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + ok := metal.AllProvisioningEventTypes[metal.ProvisioningEventType(requestPayload.Event)] + if !ok { + if checkError(request, response, utils.CurrentFuncName(), errors.New("unknown provisioning event")) { + return + } + } + + event := metal.ProvisioningEvent{ + Time: time.Now(), + Event: metal.ProvisioningEventType(requestPayload.Event), + Message: requestPayload.Message, + } + ec, err := r.ds.ProvisioningEventForMachine(id, event) + if checkError(request, response, utils.CurrentFuncName(), err) { + return + } + + err = response.WriteHeaderAndEntity(http.StatusOK, v1.NewMachineRecentProvisioningEvents(ec)) + if err != nil { + zapup.MustRootLogger().Error("Failed to send response", zap.Error(err)) + return + } +} diff --git a/cmd/metal-api/internal/service/event-service_test.go b/cmd/metal-api/internal/service/event-service_test.go new file mode 100644 index 000000000..fd8fc65ed --- /dev/null +++ b/cmd/metal-api/internal/service/event-service_test.go @@ -0,0 +1,51 @@ +package service + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + restful "github.com/emicklei/go-restful/v3" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" + v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" + "github.com/stretchr/testify/require" +) + +func TestAddProvisioningEvent(t *testing.T) { + ds, mock := datastore.InitMockDB() + testdata.InitMockDBData(mock) + + eventservice, err := NewEvent(ds, nil, nil) + require.NoError(t, err) + + container := restful.NewContainer().Add(eventservice) + event := &metal.ProvisioningEvent{ + Event: metal.ProvisioningEventPreparing, + Message: "starting metal-hammer", + } + js, _ := json.Marshal(event) + body := bytes.NewBuffer(js) + req := httptest.NewRequest("POST", "/v1/event/machine/1", body) + container = injectEditor(container, req) + req.Header.Add("Content-Type", "application/json") + w := httptest.NewRecorder() + container.ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) + var result v1.MachineRecentProvisioningEvents + err = json.NewDecoder(resp.Body).Decode(&result) + + require.Nil(t, err) + require.Equal(t, "0", result.IncompleteProvisioningCycles) + require.Len(t, result.Events, 1) + if len(result.Events) > 0 { + require.Equal(t, "starting metal-hammer", result.Events[0].Message) + require.Equal(t, string(metal.ProvisioningEventPreparing), result.Events[0].Event) + } +} diff --git a/cmd/metal-api/internal/service/machine-service.go b/cmd/metal-api/internal/service/machine-service.go index 97897ac87..0bc8dc5be 100644 --- a/cmd/metal-api/internal/service/machine-service.go +++ b/cmd/metal-api/internal/service/machine-service.go @@ -307,27 +307,6 @@ func (r machineResource) webService() *restful.WebService { Returns(http.StatusOK, "OK", v1.BootInfo{}). DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - ws.Route(ws.GET("/{id}/event"). - To(viewer(r.getProvisioningEventContainer)). - Operation("getProvisioningEventContainer"). - Doc("get the current machine provisioning event container"). - Param(ws.PathParameter("id", "identifier of the machine").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Writes(v1.MachineRecentProvisioningEvents{}). - Returns(http.StatusOK, "OK", v1.MachineRecentProvisioningEvents{}). - DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - - ws.Route(ws.POST("/{id}/event"). - To(editor(r.addProvisioningEvent)). - Operation("addProvisioningEvent"). - Doc("adds a machine provisioning event"). - Param(ws.PathParameter("id", "identifier of the machine").DataType("string")). - Metadata(restfulspec.KeyOpenAPITags, tags). - Reads(v1.MachineProvisioningEvent{}). - Writes(v1.MachineRecentProvisioningEvents{}). - Returns(http.StatusOK, "OK", v1.MachineRecentProvisioningEvents{}). - DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - ws.Route(ws.POST("/{id}/power/on"). To(editor(r.machineOn)). Operation("machineOn"). @@ -1691,8 +1670,13 @@ func (r machineResource) freeMachine(request *restful.Request, response *restful logger.Error("Failed to send response", zap.Error(err)) } - event := string(metal.ProvisioningEventPlannedReboot) - _, err = r.provisioningEventForMachine(id, v1.MachineProvisioningEvent{Time: time.Now(), Event: event, Message: "freeMachine"}) + event := metal.ProvisioningEvent{ + Time: time.Now(), + Event: metal.ProvisioningEventPlannedReboot, + Message: "freeMachine", + } + + _, err = r.ds.ProvisioningEventForMachine(id, event) if checkError(request, response, utils.CurrentFuncName(), err) { return } @@ -1918,112 +1902,6 @@ func publishDeleteEvent(publisher bus.Publisher, m *metal.Machine, logger *zap.L return nil } -func (r machineResource) getProvisioningEventContainer(request *restful.Request, response *restful.Response) { - id := request.PathParameter("id") - - // check for existence of the machine - _, err := r.ds.FindMachineByID(id) - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - - ec, err := r.ds.FindProvisioningEventContainer(id) - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - err = response.WriteHeaderAndEntity(http.StatusOK, v1.NewMachineRecentProvisioningEvents(ec)) - if err != nil { - zapup.MustRootLogger().Error("Failed to send response", zap.Error(err)) - return - } -} - -func (r machineResource) addProvisioningEvent(request *restful.Request, response *restful.Response) { - id := request.PathParameter("id") - m, err := r.ds.FindMachineByID(id) - if err != nil && !metal.IsNotFound(err) { - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - } - - // an event can actually create an empty machine. This enables us to also catch the very first PXE Booting event - // in a machine lifecycle - if m == nil { - m = &metal.Machine{ - Base: metal.Base{ - ID: id, - }, - } - err = r.ds.CreateMachine(m) - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - } - - var requestPayload v1.MachineProvisioningEvent - err = request.ReadEntity(&requestPayload) - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - ok := metal.AllProvisioningEventTypes[metal.ProvisioningEventType(requestPayload.Event)] - if !ok { - if checkError(request, response, utils.CurrentFuncName(), errors.New("unknown provisioning event")) { - return - } - } - - ec, err := r.provisioningEventForMachine(id, requestPayload) - if checkError(request, response, utils.CurrentFuncName(), err) { - return - } - - err = response.WriteHeaderAndEntity(http.StatusOK, v1.NewMachineRecentProvisioningEvents(ec)) - if err != nil { - zapup.MustRootLogger().Error("Failed to send response", zap.Error(err)) - return - } -} - -func (r machineResource) provisioningEventForMachine(machineID string, e v1.MachineProvisioningEvent) (*metal.ProvisioningEventContainer, error) { - ec, err := r.ds.FindProvisioningEventContainer(machineID) - if err != nil && !metal.IsNotFound(err) { - return nil, err - } - - if ec == nil { - ec = &metal.ProvisioningEventContainer{ - Base: metal.Base{ - ID: machineID, - }, - Liveliness: metal.MachineLivelinessAlive, - } - } - now := time.Now() - ec.LastEventTime = &now - - event := metal.ProvisioningEvent{ - Time: now, - Event: metal.ProvisioningEventType(e.Event), - Message: e.Message, - } - if event.Event == metal.ProvisioningEventAlive { - zapup.MustRootLogger().Sugar().Debugw("received provisioning alive event", "id", ec.ID) - ec.Liveliness = metal.MachineLivelinessAlive - } else if event.Event == metal.ProvisioningEventPhonedHome && len(ec.Events) > 0 && ec.Events[0].Event == metal.ProvisioningEventPhonedHome { - zapup.MustRootLogger().Sugar().Debugw("swallowing repeated phone home event", "id", ec.ID) - ec.Liveliness = metal.MachineLivelinessAlive - } else { - ec.Events = append([]metal.ProvisioningEvent{event}, ec.Events...) - ec.IncompleteProvisioningCycles = ec.CalculateIncompleteCycles(zapup.MustRootLogger().Sugar()) - ec.Liveliness = metal.MachineLivelinessAlive - } - ec.TrimEvents(metal.ProvisioningEventsInspectionLimit) - - err = r.ds.UpsertProvisioningEventContainer(ec) - return ec, err -} - // MachineLiveliness evaluates whether machines are still alive or if they have died func MachineLiveliness(ds *datastore.RethinkStore, logger *zap.SugaredLogger) error { logger.Info("machine liveliness was requested") @@ -2041,7 +1919,7 @@ func MachineLiveliness(ds *datastore.RethinkStore, logger *zap.SugaredLogger) er errs := 0 for _, m := range machines { p := liveliness[m.PartitionID] - lvlness, err := evaluateMachineLiveliness(ds, m) + lvlness, err := ds.EvaluateMachineLiveliness(m) if err != nil { logger.Errorw("cannot update liveliness", "error", err, "machine", m) errs++ @@ -2068,36 +1946,6 @@ func MachineLiveliness(ds *datastore.RethinkStore, logger *zap.SugaredLogger) er return nil } -func evaluateMachineLiveliness(ds *datastore.RethinkStore, m metal.Machine) (metal.MachineLiveliness, error) { - provisioningEvents, err := ds.FindProvisioningEventContainer(m.ID) - if err != nil { - // we have no provisioning events... we cannot tell - return metal.MachineLivelinessUnknown, fmt.Errorf("no provisioningEvents found for ID: %s", m.ID) - } - - old := *provisioningEvents - - if provisioningEvents.LastEventTime != nil { - if time.Since(*provisioningEvents.LastEventTime) > metal.MachineDeadAfter { - if m.Allocation != nil { - // the machine is either dead or the customer did turn off the phone home service - provisioningEvents.Liveliness = metal.MachineLivelinessUnknown - } else { - // the machine is just dead - provisioningEvents.Liveliness = metal.MachineLivelinessDead - } - } else { - provisioningEvents.Liveliness = metal.MachineLivelinessAlive - } - err = ds.UpdateProvisioningEventContainer(&old, provisioningEvents) - if err != nil { - return provisioningEvents.Liveliness, err - } - } - - return provisioningEvents.Liveliness, nil -} - // ResurrectMachines attempts to resurrect machines that are obviously dead func ResurrectMachines(ds *datastore.RethinkStore, publisher bus.Publisher, ep *bus.Endpoints, ipamer ipam.IPAMer, logger *zap.SugaredLogger) error { logger.Info("machine resurrection was requested") @@ -2243,8 +2091,12 @@ func (r machineResource) machineCmd(op string, cmd metal.MachineCommand, request switch op { case "machineReset", "machineOff", "machineCycle": - event := string(metal.ProvisioningEventPlannedReboot) - _, err = r.provisioningEventForMachine(id, v1.MachineProvisioningEvent{Time: time.Now(), Event: event, Message: op}) + event := metal.ProvisioningEvent{ + Time: time.Now(), + Event: metal.ProvisioningEventPlannedReboot, + Message: op, + } + _, err = r.ds.ProvisioningEventForMachine(id, event) if checkError(request, response, utils.CurrentFuncName(), err) { return } diff --git a/cmd/metal-api/internal/service/machine-service_test.go b/cmd/metal-api/internal/service/machine-service_test.go index 0087b1429..aa0c39cc3 100644 --- a/cmd/metal-api/internal/service/machine-service_test.go +++ b/cmd/metal-api/internal/service/machine-service_test.go @@ -622,41 +622,6 @@ func TestSearchMachine(t *testing.T) { require.Equal(t, testdata.Partition1.Name, *result.Partition.Name) } -func TestAddProvisioningEvent(t *testing.T) { - ds, mock := datastore.InitMockDB() - testdata.InitMockDBData(mock) - - machineservice, err := NewMachine(ds, &emptyPublisher{}, bus.DirectEndpoints(), ipam.New(goipam.New()), nil, nil, nil, nil, 0) - require.NoError(t, err) - - container := restful.NewContainer().Add(machineservice) - event := &metal.ProvisioningEvent{ - Event: metal.ProvisioningEventPreparing, - Message: "starting metal-hammer", - } - js, _ := json.Marshal(event) - body := bytes.NewBuffer(js) - req := httptest.NewRequest("POST", "/v1/machine/1/event", body) - container = injectEditor(container, req) - req.Header.Add("Content-Type", "application/json") - w := httptest.NewRecorder() - container.ServeHTTP(w, req) - - resp := w.Result() - defer resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) - var result v1.MachineRecentProvisioningEvents - err = json.NewDecoder(resp.Body).Decode(&result) - - require.Nil(t, err) - require.Equal(t, "0", result.IncompleteProvisioningCycles) - require.Len(t, result.Events, 1) - if len(result.Events) > 0 { - require.Equal(t, "starting metal-hammer", result.Events[0].Message) - require.Equal(t, string(metal.ProvisioningEventPreparing), result.Events[0].Event) - } -} - func TestOnMachine(t *testing.T) { tests := []struct { cmd metal.MachineCommand diff --git a/cmd/metal-api/main.go b/cmd/metal-api/main.go index 657942cca..55040d871 100644 --- a/cmd/metal-api/main.go +++ b/cmd/metal-api/main.go @@ -67,6 +67,7 @@ const ( var ( cfgFile string ds *datastore.RethinkStore + eventds *datastore.RethinkStore ipamer *ipam.Ipam publisherTLSConfig *bus.TLSConfig nsqer *eventbus.NSQClient @@ -215,6 +216,12 @@ func init() { rootCmd.PersistentFlags().StringP("db-user", "", "", "the database user to use") rootCmd.PersistentFlags().StringP("db-password", "", "", "the database password to use") + rootCmd.Flags().Bool("event-db-enabled", false, "store events in a separate database") + rootCmd.PersistentFlags().StringP("event-db-name", "", "metalapi", "the event database name to use") + rootCmd.PersistentFlags().StringP("event-db-addr", "", "", "the event database address string to use") + rootCmd.PersistentFlags().StringP("event-db-user", "", "", "the event database user to use") + rootCmd.PersistentFlags().StringP("event-db-password", "", "", "the event database password to use") + rootCmd.Flags().StringP("ipam-db", "", "postgres", "the database adapter to use") rootCmd.Flags().StringP("ipam-db-name", "", "metal-ipam", "the database name to use") rootCmd.Flags().StringP("ipam-db-addr", "", "", "the database address string to use") @@ -406,6 +413,15 @@ func connectDataStore(opts ...dsConnectOpt) error { viper.GetString("db-user"), viper.GetString("db-password"), ) + if viper.GetBool("event-db-enabled") { + eventds = datastore.New( + logger.Desugar(), + viper.GetString("event-db-addr"), + viper.GetString("event-db-name"), + viper.GetString("event-db-user"), + viper.GetString("event-db-password"), + ) + } } else { return fmt.Errorf("database not supported: %v", dbAdapter) } @@ -443,6 +459,30 @@ func connectDataStore(opts ...dsConnectOpt) error { } } + if !viper.GetBool("event-db-enabled") { + eventds = ds + return nil + } + + err = eventds.Connect() + if err != nil { + return fmt.Errorf("cannot connect to event data store: %w", err) + } + + if initTables { + err := eventds.Initialize() + if err != nil { + return fmt.Errorf("error initializing event data store tables: %w", err) + } + } + + if demote { + err = eventds.Demote() + if err != nil { + return fmt.Errorf("error demoting to event data store runtime user: %w", err) + } + } + return nil } @@ -705,6 +745,10 @@ func initRestServices(withauth bool) *restfulspec.Config { if err != nil { logger.Fatal(err) } + eventService, err := service.NewEvent(eventds, mdc, userGetter) + if err != nil { + logger.Fatal(err) + } restful.DefaultContainer.Add(service.NewPartition(ds, nsqer)) restful.DefaultContainer.Add(service.NewImage(ds)) @@ -720,6 +764,7 @@ func initRestServices(withauth bool) *restfulspec.Config { restful.DefaultContainer.Add(service.NewFilesystemLayout(ds)) restful.DefaultContainer.Add(service.NewSwitch(ds)) restful.DefaultContainer.Add(healthService) + restful.DefaultContainer.Add(eventService) restful.DefaultContainer.Add(rest.NewVersion(moduleName, service.BasePath)) restful.DefaultContainer.Filter(rest.RequestLogger(debug, lg)) restful.DefaultContainer.Filter(metrics.RestfulMetrics) diff --git a/spec/metal-api.json b/spec/metal-api.json index c616e4d79..ac3d618ef 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -4344,6 +4344,88 @@ "title": "metal-api" }, "paths": { + "/v1/event/machine/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "operationId": "getProvisioningEventContainer", + "parameters": [ + { + "description": "identifier of the machine", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.MachineRecentProvisioningEvents" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "get the current machine provisioning event container", + "tags": [ + "event" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "operationId": "addProvisioningEvent", + "parameters": [ + { + "description": "identifier of the machine", + "in": "path", + "name": "id", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.MachineProvisioningEvent" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.MachineRecentProvisioningEvents" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "adds a machine provisioning event", + "tags": [ + "event" + ] + } + }, "/v1/filesystemlayout": { "get": { "consumes": [ @@ -6035,88 +6117,6 @@ ] } }, - "/v1/machine/{id}/event": { - "get": { - "consumes": [ - "application/json" - ], - "operationId": "getProvisioningEventContainer", - "parameters": [ - { - "description": "identifier of the machine", - "in": "path", - "name": "id", - "required": true, - "type": "string" - } - ], - "produces": [ - "application/json" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.MachineRecentProvisioningEvents" - } - }, - "default": { - "description": "Error", - "schema": { - "$ref": "#/definitions/httperrors.HTTPErrorResponse" - } - } - }, - "summary": "get the current machine provisioning event container", - "tags": [ - "machine" - ] - }, - "post": { - "consumes": [ - "application/json" - ], - "operationId": "addProvisioningEvent", - "parameters": [ - { - "description": "identifier of the machine", - "in": "path", - "name": "id", - "required": true, - "type": "string" - }, - { - "in": "body", - "name": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1.MachineProvisioningEvent" - } - } - ], - "produces": [ - "application/json" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.MachineRecentProvisioningEvents" - } - }, - "default": { - "description": "Error", - "schema": { - "$ref": "#/definitions/httperrors.HTTPErrorResponse" - } - } - }, - "summary": "adds a machine provisioning event", - "tags": [ - "machine" - ] - } - }, "/v1/machine/{id}/finalize-allocation": { "post": { "consumes": [