Переглянути джерело

Initial functional API skeleton generated by go-swagger.

Aurelien 5 роки тому
коміт
856c6de176

+ 57 - 0
cmd/spaas-server/main.go

@@ -0,0 +1,57 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package main
+
+import (
+	"log"
+	"os"
+
+	"github.com/go-openapi/loads"
+	flags "github.com/jessevdk/go-flags"
+
+	"gem-spaas-coding-challenge/restapi"
+	"gem-spaas-coding-challenge/restapi/operations"
+)
+
+// This file was generated by the swagger tool.
+// Make sure not to overwrite this file after you generated it because all your edits would be lost!
+
+func main() {
+
+	swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
+	if err != nil {
+		log.Fatalln(err)
+	}
+
+	api := operations.NewSpaasAPI(swaggerSpec)
+	server := restapi.NewServer(api)
+	defer server.Shutdown()
+
+	parser := flags.NewParser(server, flags.Default)
+	parser.ShortDescription = "Optimal power production allocation service"
+	parser.LongDescription = "This API exposes an algorithm that computes optimal power production allocation for a given power need and power plant capacity."
+	server.ConfigureFlags()
+	for _, optsGroup := range api.CommandLineOptionsGroups {
+		_, err := parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
+		if err != nil {
+			log.Fatalln(err)
+		}
+	}
+
+	if _, err := parser.Parse(); err != nil {
+		code := 1
+		if fe, ok := err.(*flags.Error); ok {
+			if fe.Type == flags.ErrHelp {
+				code = 0
+			}
+		}
+		os.Exit(code)
+	}
+
+	server.ConfigureAPI()
+
+	if err := server.Serve(); err != nil {
+		log.Fatalln(err)
+	}
+
+}

+ 74 - 0
models/error.go

@@ -0,0 +1,74 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// Error error
+//
+// swagger:model error
+type Error struct {
+
+	// code
+	Code int64 `json:"code,omitempty"`
+
+	// message
+	// Required: true
+	Message *string `json:"message"`
+}
+
+// Validate validates this error
+func (m *Error) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMessage(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *Error) validateMessage(formats strfmt.Registry) error {
+
+	if err := validate.Required("message", "body", m.Message); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this error based on context it is used
+func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Error) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Error) UnmarshalBinary(b []byte) error {
+	var res Error
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 105 - 0
models/payload.go

@@ -0,0 +1,105 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// Payload payload
+//
+// swagger:model payload
+type Payload struct {
+
+	// fuels
+	// Required: true
+	Fuels interface{} `json:"fuels"`
+
+	// load
+	// Required: true
+	Load *float64 `json:"load"`
+
+	// powerplants
+	// Required: true
+	Powerplants []interface{} `json:"powerplants"`
+}
+
+// Validate validates this payload
+func (m *Payload) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateFuels(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateLoad(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePowerplants(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *Payload) validateFuels(formats strfmt.Registry) error {
+
+	if m.Fuels == nil {
+		return errors.Required("fuels", "body", nil)
+	}
+
+	return nil
+}
+
+func (m *Payload) validateLoad(formats strfmt.Registry) error {
+
+	if err := validate.Required("load", "body", m.Load); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Payload) validatePowerplants(formats strfmt.Registry) error {
+
+	if err := validate.Required("powerplants", "body", m.Powerplants); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this payload based on context it is used
+func (m *Payload) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Payload) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Payload) UnmarshalBinary(b []byte) error {
+	var res Payload
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 75 - 0
restapi/configure_spaas.go

@@ -0,0 +1,75 @@
+// This file is safe to edit. Once it exists it will not be overwritten
+
+package restapi
+
+import (
+	"crypto/tls"
+	"net/http"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/runtime"
+	"github.com/go-openapi/runtime/middleware"
+
+	"gem-spaas-coding-challenge/restapi/operations"
+)
+
+//go:generate swagger generate server --target ../../gem-spaas-coding-challenge --name Spaas --spec ../swagger.yml --principal interface{}
+
+func configureFlags(api *operations.SpaasAPI) {
+	// api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
+}
+
+func configureAPI(api *operations.SpaasAPI) http.Handler {
+	// configure the api here
+	api.ServeError = errors.ServeError
+
+	// Set your custom logger if needed. Default one is log.Printf
+	// Expected interface func(string, ...interface{})
+	//
+	// Example:
+	// api.Logger = log.Printf
+
+	api.UseSwaggerUI()
+	// To continue using redoc as your UI, uncomment the following line
+	// api.UseRedoc()
+
+	api.JSONConsumer = runtime.JSONConsumer()
+
+	api.JSONProducer = runtime.JSONProducer()
+
+	if api.ProductionPlanHandler == nil {
+		api.ProductionPlanHandler = operations.ProductionPlanHandlerFunc(func(params operations.ProductionPlanParams) middleware.Responder {
+			return middleware.NotImplemented("operation operations.ProductionPlan has not yet been implemented")
+		})
+	}
+
+	api.PreServerShutdown = func() {}
+
+	api.ServerShutdown = func() {}
+
+	return setupGlobalMiddleware(api.Serve(setupMiddlewares))
+}
+
+// The TLS configuration before HTTPS server starts.
+func configureTLS(tlsConfig *tls.Config) {
+	// Make all necessary changes to the TLS configuration here.
+}
+
+// As soon as server is initialized but not run yet, this function will be called.
+// If you need to modify a config, store server instance to stop it individually later, this is the place.
+// This function can be called multiple times, depending on the number of serving schemes.
+// scheme value will be set accordingly: "http", "https" or "unix".
+func configureServer(s *http.Server, scheme, addr string) {
+}
+
+// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
+// The middleware executes after routing but before authentication, binding and validation.
+func setupMiddlewares(handler http.Handler) http.Handler {
+	return handler
+}
+
+// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
+// So this is a good place to plug in a panic handling middleware, logging and metrics.
+func setupGlobalMiddleware(handler http.Handler) http.Handler {
+	return handler
+}

+ 21 - 0
restapi/doc.go

@@ -0,0 +1,21 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// Package restapi Optimal power production allocation service
+//
+//  This API exposes an algorithm that computes optimal power production allocation for a given power need and power plant capacity.
+//  Schemes:
+//    http
+//  Host: localhost
+//  BasePath: /
+//  Version: 0.0.1
+//
+//  Consumes:
+//    - application/eu.reverservices.gem.spaas.v1+json
+//    - application/json
+//
+//  Produces:
+//    - application/eu.reverservices.gem.spaas.v1+json
+//    - application/json
+//
+// swagger:meta
+package restapi

+ 216 - 0
restapi/embedded_spec.go

@@ -0,0 +1,216 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package restapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"encoding/json"
+)
+
+var (
+	// SwaggerJSON embedded version of the swagger document used at generation time
+	SwaggerJSON json.RawMessage
+	// FlatSwaggerJSON embedded flattened version of the swagger document used at generation time
+	FlatSwaggerJSON json.RawMessage
+)
+
+func init() {
+	SwaggerJSON = json.RawMessage([]byte(`{
+  "consumes": [
+    "application/eu.reverservices.gem.spaas.v1+json"
+  ],
+  "produces": [
+    "application/eu.reverservices.gem.spaas.v1+json"
+  ],
+  "schemes": [
+    "http"
+  ],
+  "swagger": "2.0",
+  "info": {
+    "description": "This API exposes an algorithm that computes optimal power production allocation for a given power need and power plant capacity.",
+    "title": "Optimal power production allocation service",
+    "version": "0.0.1"
+  },
+  "paths": {
+    "/productionplan": {
+      "post": {
+        "description": "Compute the optimal production plan for given load and capacity.\n",
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "summary": "compute production plan",
+        "operationId": "ProductionPlan",
+        "parameters": [
+          {
+            "name": "payload format",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/payload"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "returns optimal production plan",
+            "schema": {
+              "type": "array",
+              "items": {
+                "type": "object"
+              }
+            }
+          },
+          "default": {
+            "description": "generic error response",
+            "schema": {
+              "$ref": "#/definitions/error"
+            }
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "error": {
+      "type": "object",
+      "required": [
+        "message"
+      ],
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "message": {
+          "type": "string"
+        }
+      }
+    },
+    "payload": {
+      "type": "object",
+      "required": [
+        "load",
+        "fuels",
+        "powerplants"
+      ],
+      "properties": {
+        "fuels": {
+          "type": "object"
+        },
+        "load": {
+          "type": "number",
+          "format": "float64"
+        },
+        "powerplants": {
+          "type": "array",
+          "items": {
+            "type": "object"
+          }
+        }
+      }
+    }
+  }
+}`))
+	FlatSwaggerJSON = json.RawMessage([]byte(`{
+  "consumes": [
+    "application/eu.reverservices.gem.spaas.v1+json"
+  ],
+  "produces": [
+    "application/eu.reverservices.gem.spaas.v1+json"
+  ],
+  "schemes": [
+    "http"
+  ],
+  "swagger": "2.0",
+  "info": {
+    "description": "This API exposes an algorithm that computes optimal power production allocation for a given power need and power plant capacity.",
+    "title": "Optimal power production allocation service",
+    "version": "0.0.1"
+  },
+  "paths": {
+    "/productionplan": {
+      "post": {
+        "description": "Compute the optimal production plan for given load and capacity.\n",
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "summary": "compute production plan",
+        "operationId": "ProductionPlan",
+        "parameters": [
+          {
+            "name": "payload format",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/payload"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "returns optimal production plan",
+            "schema": {
+              "type": "array",
+              "items": {
+                "type": "object"
+              }
+            }
+          },
+          "default": {
+            "description": "generic error response",
+            "schema": {
+              "$ref": "#/definitions/error"
+            }
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "error": {
+      "type": "object",
+      "required": [
+        "message"
+      ],
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "message": {
+          "type": "string"
+        }
+      }
+    },
+    "payload": {
+      "type": "object",
+      "required": [
+        "load",
+        "fuels",
+        "powerplants"
+      ],
+      "properties": {
+        "fuels": {
+          "type": "object"
+        },
+        "load": {
+          "type": "number",
+          "format": "float64"
+        },
+        "powerplants": {
+          "type": "array",
+          "items": {
+            "type": "object"
+          }
+        }
+      }
+    }
+  }
+}`))
+}

+ 59 - 0
restapi/operations/production_plan.go

@@ -0,0 +1,59 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+	"net/http"
+
+	"github.com/go-openapi/runtime/middleware"
+)
+
+// ProductionPlanHandlerFunc turns a function with the right signature into a production plan handler
+type ProductionPlanHandlerFunc func(ProductionPlanParams) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn ProductionPlanHandlerFunc) Handle(params ProductionPlanParams) middleware.Responder {
+	return fn(params)
+}
+
+// ProductionPlanHandler interface for that can handle valid production plan params
+type ProductionPlanHandler interface {
+	Handle(ProductionPlanParams) middleware.Responder
+}
+
+// NewProductionPlan creates a new http.Handler for the production plan operation
+func NewProductionPlan(ctx *middleware.Context, handler ProductionPlanHandler) *ProductionPlan {
+	return &ProductionPlan{Context: ctx, Handler: handler}
+}
+
+/*ProductionPlan swagger:route POST /productionplan productionPlan
+
+compute production plan
+
+Compute the optimal production plan for given load and capacity.
+
+
+*/
+type ProductionPlan struct {
+	Context *middleware.Context
+	Handler ProductionPlanHandler
+}
+
+func (o *ProductionPlan) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+	route, rCtx, _ := o.Context.RouteInfo(r)
+	if rCtx != nil {
+		r = rCtx
+	}
+	var Params = NewProductionPlanParams()
+	if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+		o.Context.Respond(rw, r, route.Produces, route, err)
+		return
+	}
+
+	res := o.Handler.Handle(Params) // actually handle the request
+	o.Context.Respond(rw, r, route.Produces, route, res)
+
+}

+ 76 - 0
restapi/operations/production_plan_parameters.go

@@ -0,0 +1,76 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/runtime"
+	"github.com/go-openapi/runtime/middleware"
+	"github.com/go-openapi/validate"
+
+	"gem-spaas-coding-challenge/models"
+)
+
+// NewProductionPlanParams creates a new ProductionPlanParams object
+// no default values defined in spec.
+func NewProductionPlanParams() ProductionPlanParams {
+
+	return ProductionPlanParams{}
+}
+
+// ProductionPlanParams contains all the bound params for the production plan operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters ProductionPlan
+type ProductionPlanParams struct {
+
+	// HTTP Request Object
+	HTTPRequest *http.Request `json:"-"`
+
+	/*
+	  In: body
+	*/
+	PayloadFormat *models.Payload
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewProductionPlanParams() beforehand.
+func (o *ProductionPlanParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+	var res []error
+
+	o.HTTPRequest = r
+
+	if runtime.HasBody(r) {
+		defer r.Body.Close()
+		var body models.Payload
+		if err := route.Consumer.Consume(r.Body, &body); err != nil {
+			res = append(res, errors.NewParseError("payloadFormat", "body", "", err))
+		} else {
+			// validate body object
+			if err := body.Validate(route.Formats); err != nil {
+				res = append(res, err)
+			}
+
+			ctx := validate.WithOperationRequest(context.Background())
+			if err := body.ContextValidate(ctx, route.Formats); err != nil {
+				res = append(res, err)
+			}
+
+			if len(res) == 0 {
+				o.PayloadFormat = &body
+			}
+		}
+	}
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 119 - 0
restapi/operations/production_plan_responses.go

@@ -0,0 +1,119 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"net/http"
+
+	"github.com/go-openapi/runtime"
+
+	"gem-spaas-coding-challenge/models"
+)
+
+// ProductionPlanOKCode is the HTTP code returned for type ProductionPlanOK
+const ProductionPlanOKCode int = 200
+
+/*ProductionPlanOK returns optimal production plan
+
+swagger:response productionPlanOK
+*/
+type ProductionPlanOK struct {
+
+	/*
+	  In: Body
+	*/
+	Payload []interface{} `json:"body,omitempty"`
+}
+
+// NewProductionPlanOK creates ProductionPlanOK with default headers values
+func NewProductionPlanOK() *ProductionPlanOK {
+
+	return &ProductionPlanOK{}
+}
+
+// WithPayload adds the payload to the production plan o k response
+func (o *ProductionPlanOK) WithPayload(payload []interface{}) *ProductionPlanOK {
+	o.Payload = payload
+	return o
+}
+
+// SetPayload sets the payload to the production plan o k response
+func (o *ProductionPlanOK) SetPayload(payload []interface{}) {
+	o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *ProductionPlanOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+	rw.WriteHeader(200)
+	payload := o.Payload
+	if payload == nil {
+		// return empty array
+		payload = make([]interface{}, 0, 50)
+	}
+
+	if err := producer.Produce(rw, payload); err != nil {
+		panic(err) // let the recovery middleware deal with this
+	}
+}
+
+/*ProductionPlanDefault generic error response
+
+swagger:response productionPlanDefault
+*/
+type ProductionPlanDefault struct {
+	_statusCode int
+
+	/*
+	  In: Body
+	*/
+	Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewProductionPlanDefault creates ProductionPlanDefault with default headers values
+func NewProductionPlanDefault(code int) *ProductionPlanDefault {
+	if code <= 0 {
+		code = 500
+	}
+
+	return &ProductionPlanDefault{
+		_statusCode: code,
+	}
+}
+
+// WithStatusCode adds the status to the production plan default response
+func (o *ProductionPlanDefault) WithStatusCode(code int) *ProductionPlanDefault {
+	o._statusCode = code
+	return o
+}
+
+// SetStatusCode sets the status to the production plan default response
+func (o *ProductionPlanDefault) SetStatusCode(code int) {
+	o._statusCode = code
+}
+
+// WithPayload adds the payload to the production plan default response
+func (o *ProductionPlanDefault) WithPayload(payload *models.Error) *ProductionPlanDefault {
+	o.Payload = payload
+	return o
+}
+
+// SetPayload sets the payload to the production plan default response
+func (o *ProductionPlanDefault) SetPayload(payload *models.Error) {
+	o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *ProductionPlanDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+	rw.WriteHeader(o._statusCode)
+	if o.Payload != nil {
+		payload := o.Payload
+		if err := producer.Produce(rw, payload); err != nil {
+			panic(err) // let the recovery middleware deal with this
+		}
+	}
+}

+ 84 - 0
restapi/operations/production_plan_urlbuilder.go

@@ -0,0 +1,84 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+	"errors"
+	"net/url"
+	golangswaggerpaths "path"
+)
+
+// ProductionPlanURL generates an URL for the production plan operation
+type ProductionPlanURL struct {
+	_basePath string
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *ProductionPlanURL) WithBasePath(bp string) *ProductionPlanURL {
+	o.SetBasePath(bp)
+	return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *ProductionPlanURL) SetBasePath(bp string) {
+	o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *ProductionPlanURL) Build() (*url.URL, error) {
+	var _result url.URL
+
+	var _path = "/productionplan"
+
+	_basePath := o._basePath
+	_result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+	return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *ProductionPlanURL) Must(u *url.URL, err error) *url.URL {
+	if err != nil {
+		panic(err)
+	}
+	if u == nil {
+		panic("url can't be nil")
+	}
+	return u
+}
+
+// String returns the string representation of the path with query string
+func (o *ProductionPlanURL) String() string {
+	return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *ProductionPlanURL) BuildFull(scheme, host string) (*url.URL, error) {
+	if scheme == "" {
+		return nil, errors.New("scheme is required for a full url on ProductionPlanURL")
+	}
+	if host == "" {
+		return nil, errors.New("host is required for a full url on ProductionPlanURL")
+	}
+
+	base, err := o.Build()
+	if err != nil {
+		return nil, err
+	}
+
+	base.Scheme = scheme
+	base.Host = host
+	return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *ProductionPlanURL) StringFull(scheme, host string) string {
+	return o.Must(o.BuildFull(scheme, host)).String()
+}

+ 304 - 0
restapi/operations/spaas_api.go

@@ -0,0 +1,304 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package operations
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/loads"
+	"github.com/go-openapi/runtime"
+	"github.com/go-openapi/runtime/middleware"
+	"github.com/go-openapi/runtime/security"
+	"github.com/go-openapi/spec"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// NewSpaasAPI creates a new Spaas instance
+func NewSpaasAPI(spec *loads.Document) *SpaasAPI {
+	return &SpaasAPI{
+		handlers:            make(map[string]map[string]http.Handler),
+		formats:             strfmt.Default,
+		defaultConsumes:     "application/json",
+		defaultProduces:     "application/json",
+		customConsumers:     make(map[string]runtime.Consumer),
+		customProducers:     make(map[string]runtime.Producer),
+		PreServerShutdown:   func() {},
+		ServerShutdown:      func() {},
+		spec:                spec,
+		useSwaggerUI:        false,
+		ServeError:          errors.ServeError,
+		BasicAuthenticator:  security.BasicAuth,
+		APIKeyAuthenticator: security.APIKeyAuth,
+		BearerAuthenticator: security.BearerAuth,
+
+		JSONConsumer: runtime.JSONConsumer(),
+
+		JSONProducer: runtime.JSONProducer(),
+
+		ProductionPlanHandler: ProductionPlanHandlerFunc(func(params ProductionPlanParams) middleware.Responder {
+			return middleware.NotImplemented("operation ProductionPlan has not yet been implemented")
+		}),
+	}
+}
+
+/*SpaasAPI This API exposes an algorithm that computes optimal power production allocation for a given power need and power plant capacity. */
+type SpaasAPI struct {
+	spec            *loads.Document
+	context         *middleware.Context
+	handlers        map[string]map[string]http.Handler
+	formats         strfmt.Registry
+	customConsumers map[string]runtime.Consumer
+	customProducers map[string]runtime.Producer
+	defaultConsumes string
+	defaultProduces string
+	Middleware      func(middleware.Builder) http.Handler
+	useSwaggerUI    bool
+
+	// BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function.
+	// It has a default implementation in the security package, however you can replace it for your particular usage.
+	BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator
+	// APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function.
+	// It has a default implementation in the security package, however you can replace it for your particular usage.
+	APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator
+	// BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function.
+	// It has a default implementation in the security package, however you can replace it for your particular usage.
+	BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator
+
+	// JSONConsumer registers a consumer for the following mime types:
+	//   - application/eu.reverservices.gem.spaas.v1+json
+	//   - application/json
+	JSONConsumer runtime.Consumer
+
+	// JSONProducer registers a producer for the following mime types:
+	//   - application/eu.reverservices.gem.spaas.v1+json
+	//   - application/json
+	JSONProducer runtime.Producer
+
+	// ProductionPlanHandler sets the operation handler for the production plan operation
+	ProductionPlanHandler ProductionPlanHandler
+	// ServeError is called when an error is received, there is a default handler
+	// but you can set your own with this
+	ServeError func(http.ResponseWriter, *http.Request, error)
+
+	// PreServerShutdown is called before the HTTP(S) server is shutdown
+	// This allows for custom functions to get executed before the HTTP(S) server stops accepting traffic
+	PreServerShutdown func()
+
+	// ServerShutdown is called when the HTTP(S) server is shut down and done
+	// handling all active connections and does not accept connections any more
+	ServerShutdown func()
+
+	// Custom command line argument groups with their descriptions
+	CommandLineOptionsGroups []swag.CommandLineOptionsGroup
+
+	// User defined logger function.
+	Logger func(string, ...interface{})
+}
+
+// UseRedoc for documentation at /docs
+func (o *SpaasAPI) UseRedoc() {
+	o.useSwaggerUI = false
+}
+
+// UseSwaggerUI for documentation at /docs
+func (o *SpaasAPI) UseSwaggerUI() {
+	o.useSwaggerUI = true
+}
+
+// SetDefaultProduces sets the default produces media type
+func (o *SpaasAPI) SetDefaultProduces(mediaType string) {
+	o.defaultProduces = mediaType
+}
+
+// SetDefaultConsumes returns the default consumes media type
+func (o *SpaasAPI) SetDefaultConsumes(mediaType string) {
+	o.defaultConsumes = mediaType
+}
+
+// SetSpec sets a spec that will be served for the clients.
+func (o *SpaasAPI) SetSpec(spec *loads.Document) {
+	o.spec = spec
+}
+
+// DefaultProduces returns the default produces media type
+func (o *SpaasAPI) DefaultProduces() string {
+	return o.defaultProduces
+}
+
+// DefaultConsumes returns the default consumes media type
+func (o *SpaasAPI) DefaultConsumes() string {
+	return o.defaultConsumes
+}
+
+// Formats returns the registered string formats
+func (o *SpaasAPI) Formats() strfmt.Registry {
+	return o.formats
+}
+
+// RegisterFormat registers a custom format validator
+func (o *SpaasAPI) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) {
+	o.formats.Add(name, format, validator)
+}
+
+// Validate validates the registrations in the SpaasAPI
+func (o *SpaasAPI) Validate() error {
+	var unregistered []string
+
+	if o.JSONConsumer == nil {
+		unregistered = append(unregistered, "JSONConsumer")
+	}
+
+	if o.JSONProducer == nil {
+		unregistered = append(unregistered, "JSONProducer")
+	}
+
+	if o.ProductionPlanHandler == nil {
+		unregistered = append(unregistered, "ProductionPlanHandler")
+	}
+
+	if len(unregistered) > 0 {
+		return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", "))
+	}
+
+	return nil
+}
+
+// ServeErrorFor gets a error handler for a given operation id
+func (o *SpaasAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
+	return o.ServeError
+}
+
+// AuthenticatorsFor gets the authenticators for the specified security schemes
+func (o *SpaasAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
+	return nil
+}
+
+// Authorizer returns the registered authorizer
+func (o *SpaasAPI) Authorizer() runtime.Authorizer {
+	return nil
+}
+
+// ConsumersFor gets the consumers for the specified media types.
+// MIME type parameters are ignored here.
+func (o *SpaasAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
+	result := make(map[string]runtime.Consumer, len(mediaTypes))
+	for _, mt := range mediaTypes {
+		switch mt {
+		case "application/eu.reverservices.gem.spaas.v1+json":
+			result["application/eu.reverservices.gem.spaas.v1+json"] = o.JSONConsumer
+		case "application/json":
+			result["application/json"] = o.JSONConsumer
+		}
+
+		if c, ok := o.customConsumers[mt]; ok {
+			result[mt] = c
+		}
+	}
+	return result
+}
+
+// ProducersFor gets the producers for the specified media types.
+// MIME type parameters are ignored here.
+func (o *SpaasAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
+	result := make(map[string]runtime.Producer, len(mediaTypes))
+	for _, mt := range mediaTypes {
+		switch mt {
+		case "application/eu.reverservices.gem.spaas.v1+json":
+			result["application/eu.reverservices.gem.spaas.v1+json"] = o.JSONProducer
+		case "application/json":
+			result["application/json"] = o.JSONProducer
+		}
+
+		if p, ok := o.customProducers[mt]; ok {
+			result[mt] = p
+		}
+	}
+	return result
+}
+
+// HandlerFor gets a http.Handler for the provided operation method and path
+func (o *SpaasAPI) HandlerFor(method, path string) (http.Handler, bool) {
+	if o.handlers == nil {
+		return nil, false
+	}
+	um := strings.ToUpper(method)
+	if _, ok := o.handlers[um]; !ok {
+		return nil, false
+	}
+	if path == "/" {
+		path = ""
+	}
+	h, ok := o.handlers[um][path]
+	return h, ok
+}
+
+// Context returns the middleware context for the spaas API
+func (o *SpaasAPI) Context() *middleware.Context {
+	if o.context == nil {
+		o.context = middleware.NewRoutableContext(o.spec, o, nil)
+	}
+
+	return o.context
+}
+
+func (o *SpaasAPI) initHandlerCache() {
+	o.Context() // don't care about the result, just that the initialization happened
+	if o.handlers == nil {
+		o.handlers = make(map[string]map[string]http.Handler)
+	}
+
+	if o.handlers["POST"] == nil {
+		o.handlers["POST"] = make(map[string]http.Handler)
+	}
+	o.handlers["POST"]["/productionplan"] = NewProductionPlan(o.context, o.ProductionPlanHandler)
+}
+
+// Serve creates a http handler to serve the API over HTTP
+// can be used directly in http.ListenAndServe(":8000", api.Serve(nil))
+func (o *SpaasAPI) Serve(builder middleware.Builder) http.Handler {
+	o.Init()
+
+	if o.Middleware != nil {
+		return o.Middleware(builder)
+	}
+	if o.useSwaggerUI {
+		return o.context.APIHandlerSwaggerUI(builder)
+	}
+	return o.context.APIHandler(builder)
+}
+
+// Init allows you to just initialize the handler cache, you can then recompose the middleware as you see fit
+func (o *SpaasAPI) Init() {
+	if len(o.handlers) == 0 {
+		o.initHandlerCache()
+	}
+}
+
+// RegisterConsumer allows you to add (or override) a consumer for a media type.
+func (o *SpaasAPI) RegisterConsumer(mediaType string, consumer runtime.Consumer) {
+	o.customConsumers[mediaType] = consumer
+}
+
+// RegisterProducer allows you to add (or override) a producer for a media type.
+func (o *SpaasAPI) RegisterProducer(mediaType string, producer runtime.Producer) {
+	o.customProducers[mediaType] = producer
+}
+
+// AddMiddlewareFor adds a http middleware to existing handler
+func (o *SpaasAPI) AddMiddlewareFor(method, path string, builder middleware.Builder) {
+	um := strings.ToUpper(method)
+	if path == "/" {
+		path = ""
+	}
+	o.Init()
+	if h, ok := o.handlers[um][path]; ok {
+		o.handlers[method][path] = builder(h)
+	}
+}

+ 511 - 0
restapi/server.go

@@ -0,0 +1,511 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package restapi
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"os/signal"
+	"strconv"
+	"sync"
+	"sync/atomic"
+	"syscall"
+	"time"
+
+	"github.com/go-openapi/runtime/flagext"
+	"github.com/go-openapi/swag"
+	flags "github.com/jessevdk/go-flags"
+	"golang.org/x/net/netutil"
+
+	"gem-spaas-coding-challenge/restapi/operations"
+)
+
+const (
+	schemeHTTP  = "http"
+	schemeHTTPS = "https"
+	schemeUnix  = "unix"
+)
+
+var defaultSchemes []string
+
+func init() {
+	defaultSchemes = []string{
+		schemeHTTP,
+	}
+}
+
+// NewServer creates a new api spaas server but does not configure it
+func NewServer(api *operations.SpaasAPI) *Server {
+	s := new(Server)
+
+	s.shutdown = make(chan struct{})
+	s.api = api
+	s.interrupt = make(chan os.Signal, 1)
+	return s
+}
+
+// ConfigureAPI configures the API and handlers.
+func (s *Server) ConfigureAPI() {
+	if s.api != nil {
+		s.handler = configureAPI(s.api)
+	}
+}
+
+// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
+func (s *Server) ConfigureFlags() {
+	if s.api != nil {
+		configureFlags(s.api)
+	}
+}
+
+// Server for the spaas API
+type Server struct {
+	EnabledListeners []string         `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`
+	CleanupTimeout   time.Duration    `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
+	GracefulTimeout  time.Duration    `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
+	MaxHeaderSize    flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
+
+	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/spaas.sock"`
+	domainSocketL net.Listener
+
+	Host         string        `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
+	Port         int           `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
+	ListenLimit  int           `long:"listen-limit" description:"limit the number of outstanding requests"`
+	KeepAlive    time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
+	ReadTimeout  time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
+	WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
+	httpServerL  net.Listener
+
+	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
+	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
+	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
+	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
+	TLSCACertificate  flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
+	TLSListenLimit    int            `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
+	TLSKeepAlive      time.Duration  `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
+	TLSReadTimeout    time.Duration  `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
+	TLSWriteTimeout   time.Duration  `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
+	httpsServerL      net.Listener
+
+	api          *operations.SpaasAPI
+	handler      http.Handler
+	hasListeners bool
+	shutdown     chan struct{}
+	shuttingDown int32
+	interrupted  bool
+	interrupt    chan os.Signal
+}
+
+// Logf logs message either via defined user logger or via system one if no user logger is defined.
+func (s *Server) Logf(f string, args ...interface{}) {
+	if s.api != nil && s.api.Logger != nil {
+		s.api.Logger(f, args...)
+	} else {
+		log.Printf(f, args...)
+	}
+}
+
+// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
+// Exits with non-zero status after printing
+func (s *Server) Fatalf(f string, args ...interface{}) {
+	if s.api != nil && s.api.Logger != nil {
+		s.api.Logger(f, args...)
+		os.Exit(1)
+	} else {
+		log.Fatalf(f, args...)
+	}
+}
+
+// SetAPI configures the server with the specified API. Needs to be called before Serve
+func (s *Server) SetAPI(api *operations.SpaasAPI) {
+	if api == nil {
+		s.api = nil
+		s.handler = nil
+		return
+	}
+
+	s.api = api
+	s.handler = configureAPI(api)
+}
+
+func (s *Server) hasScheme(scheme string) bool {
+	schemes := s.EnabledListeners
+	if len(schemes) == 0 {
+		schemes = defaultSchemes
+	}
+
+	for _, v := range schemes {
+		if v == scheme {
+			return true
+		}
+	}
+	return false
+}
+
+// Serve the api
+func (s *Server) Serve() (err error) {
+	if !s.hasListeners {
+		if err = s.Listen(); err != nil {
+			return err
+		}
+	}
+
+	// set default handler, if none is set
+	if s.handler == nil {
+		if s.api == nil {
+			return errors.New("can't create the default handler, as no api is set")
+		}
+
+		s.SetHandler(s.api.Serve(nil))
+	}
+
+	wg := new(sync.WaitGroup)
+	once := new(sync.Once)
+	signalNotify(s.interrupt)
+	go handleInterrupt(once, s)
+
+	servers := []*http.Server{}
+
+	if s.hasScheme(schemeUnix) {
+		domainSocket := new(http.Server)
+		domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize)
+		domainSocket.Handler = s.handler
+		if int64(s.CleanupTimeout) > 0 {
+			domainSocket.IdleTimeout = s.CleanupTimeout
+		}
+
+		configureServer(domainSocket, "unix", string(s.SocketPath))
+
+		servers = append(servers, domainSocket)
+		wg.Add(1)
+		s.Logf("Serving spaas at unix://%s", s.SocketPath)
+		go func(l net.Listener) {
+			defer wg.Done()
+			if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
+				s.Fatalf("%v", err)
+			}
+			s.Logf("Stopped serving spaas at unix://%s", s.SocketPath)
+		}(s.domainSocketL)
+	}
+
+	if s.hasScheme(schemeHTTP) {
+		httpServer := new(http.Server)
+		httpServer.MaxHeaderBytes = int(s.MaxHeaderSize)
+		httpServer.ReadTimeout = s.ReadTimeout
+		httpServer.WriteTimeout = s.WriteTimeout
+		httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
+		if s.ListenLimit > 0 {
+			s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
+		}
+
+		if int64(s.CleanupTimeout) > 0 {
+			httpServer.IdleTimeout = s.CleanupTimeout
+		}
+
+		httpServer.Handler = s.handler
+
+		configureServer(httpServer, "http", s.httpServerL.Addr().String())
+
+		servers = append(servers, httpServer)
+		wg.Add(1)
+		s.Logf("Serving spaas at http://%s", s.httpServerL.Addr())
+		go func(l net.Listener) {
+			defer wg.Done()
+			if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
+				s.Fatalf("%v", err)
+			}
+			s.Logf("Stopped serving spaas at http://%s", l.Addr())
+		}(s.httpServerL)
+	}
+
+	if s.hasScheme(schemeHTTPS) {
+		httpsServer := new(http.Server)
+		httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize)
+		httpsServer.ReadTimeout = s.TLSReadTimeout
+		httpsServer.WriteTimeout = s.TLSWriteTimeout
+		httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
+		if s.TLSListenLimit > 0 {
+			s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
+		}
+		if int64(s.CleanupTimeout) > 0 {
+			httpsServer.IdleTimeout = s.CleanupTimeout
+		}
+		httpsServer.Handler = s.handler
+
+		// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
+		httpsServer.TLSConfig = &tls.Config{
+			// Causes servers to use Go's default ciphersuite preferences,
+			// which are tuned to avoid attacks. Does nothing on clients.
+			PreferServerCipherSuites: true,
+			// Only use curves which have assembly implementations
+			// https://github.com/golang/go/tree/master/src/crypto/elliptic
+			CurvePreferences: []tls.CurveID{tls.CurveP256},
+			// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+			NextProtos: []string{"h2", "http/1.1"},
+			// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
+			MinVersion: tls.VersionTLS12,
+			// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
+			CipherSuites: []uint16{
+				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+			},
+		}
+
+		// build standard config from server options
+		if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
+			httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
+			httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))
+			if err != nil {
+				return err
+			}
+		}
+
+		if s.TLSCACertificate != "" {
+			// include specified CA certificate
+			caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate))
+			if caCertErr != nil {
+				return caCertErr
+			}
+			caCertPool := x509.NewCertPool()
+			ok := caCertPool.AppendCertsFromPEM(caCert)
+			if !ok {
+				return fmt.Errorf("cannot parse CA certificate")
+			}
+			httpsServer.TLSConfig.ClientCAs = caCertPool
+			httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
+		}
+
+		// call custom TLS configurator
+		configureTLS(httpsServer.TLSConfig)
+
+		if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
+			// after standard and custom config are passed, this ends up with no certificate
+			if s.TLSCertificate == "" {
+				if s.TLSCertificateKey == "" {
+					s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
+				}
+				s.Fatalf("the required flag `--tls-certificate` was not specified")
+			}
+			if s.TLSCertificateKey == "" {
+				s.Fatalf("the required flag `--tls-key` was not specified")
+			}
+			// this happens with a wrong custom TLS configurator
+			s.Fatalf("no certificate was configured for TLS")
+		}
+
+		// must have at least one certificate or panics
+		httpsServer.TLSConfig.BuildNameToCertificate()
+
+		configureServer(httpsServer, "https", s.httpsServerL.Addr().String())
+
+		servers = append(servers, httpsServer)
+		wg.Add(1)
+		s.Logf("Serving spaas at https://%s", s.httpsServerL.Addr())
+		go func(l net.Listener) {
+			defer wg.Done()
+			if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
+				s.Fatalf("%v", err)
+			}
+			s.Logf("Stopped serving spaas at https://%s", l.Addr())
+		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
+	}
+
+	wg.Add(1)
+	go s.handleShutdown(wg, &servers)
+
+	wg.Wait()
+	return nil
+}
+
+// Listen creates the listeners for the server
+func (s *Server) Listen() error {
+	if s.hasListeners { // already done this
+		return nil
+	}
+
+	if s.hasScheme(schemeHTTPS) {
+		// Use http host if https host wasn't defined
+		if s.TLSHost == "" {
+			s.TLSHost = s.Host
+		}
+		// Use http listen limit if https listen limit wasn't defined
+		if s.TLSListenLimit == 0 {
+			s.TLSListenLimit = s.ListenLimit
+		}
+		// Use http tcp keep alive if https tcp keep alive wasn't defined
+		if int64(s.TLSKeepAlive) == 0 {
+			s.TLSKeepAlive = s.KeepAlive
+		}
+		// Use http read timeout if https read timeout wasn't defined
+		if int64(s.TLSReadTimeout) == 0 {
+			s.TLSReadTimeout = s.ReadTimeout
+		}
+		// Use http write timeout if https write timeout wasn't defined
+		if int64(s.TLSWriteTimeout) == 0 {
+			s.TLSWriteTimeout = s.WriteTimeout
+		}
+	}
+
+	if s.hasScheme(schemeUnix) {
+		domSockListener, err := net.Listen("unix", string(s.SocketPath))
+		if err != nil {
+			return err
+		}
+		s.domainSocketL = domSockListener
+	}
+
+	if s.hasScheme(schemeHTTP) {
+		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
+		if err != nil {
+			return err
+		}
+
+		h, p, err := swag.SplitHostPort(listener.Addr().String())
+		if err != nil {
+			return err
+		}
+		s.Host = h
+		s.Port = p
+		s.httpServerL = listener
+	}
+
+	if s.hasScheme(schemeHTTPS) {
+		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
+		if err != nil {
+			return err
+		}
+
+		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
+		if err != nil {
+			return err
+		}
+		s.TLSHost = sh
+		s.TLSPort = sp
+		s.httpsServerL = tlsListener
+	}
+
+	s.hasListeners = true
+	return nil
+}
+
+// Shutdown server and clean up resources
+func (s *Server) Shutdown() error {
+	if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
+		close(s.shutdown)
+	}
+	return nil
+}
+
+func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
+	// wg.Done must occur last, after s.api.ServerShutdown()
+	// (to preserve old behaviour)
+	defer wg.Done()
+
+	<-s.shutdown
+
+	servers := *serversPtr
+
+	ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
+	defer cancel()
+
+	// first execute the pre-shutdown hook
+	s.api.PreServerShutdown()
+
+	shutdownChan := make(chan bool)
+	for i := range servers {
+		server := servers[i]
+		go func() {
+			var success bool
+			defer func() {
+				shutdownChan <- success
+			}()
+			if err := server.Shutdown(ctx); err != nil {
+				// Error from closing listeners, or context timeout:
+				s.Logf("HTTP server Shutdown: %v", err)
+			} else {
+				success = true
+			}
+		}()
+	}
+
+	// Wait until all listeners have successfully shut down before calling ServerShutdown
+	success := true
+	for range servers {
+		success = success && <-shutdownChan
+	}
+	if success {
+		s.api.ServerShutdown()
+	}
+}
+
+// GetHandler returns a handler useful for testing
+func (s *Server) GetHandler() http.Handler {
+	return s.handler
+}
+
+// SetHandler allows for setting a http handler on this server
+func (s *Server) SetHandler(handler http.Handler) {
+	s.handler = handler
+}
+
+// UnixListener returns the domain socket listener
+func (s *Server) UnixListener() (net.Listener, error) {
+	if !s.hasListeners {
+		if err := s.Listen(); err != nil {
+			return nil, err
+		}
+	}
+	return s.domainSocketL, nil
+}
+
+// HTTPListener returns the http listener
+func (s *Server) HTTPListener() (net.Listener, error) {
+	if !s.hasListeners {
+		if err := s.Listen(); err != nil {
+			return nil, err
+		}
+	}
+	return s.httpServerL, nil
+}
+
+// TLSListener returns the https listener
+func (s *Server) TLSListener() (net.Listener, error) {
+	if !s.hasListeners {
+		if err := s.Listen(); err != nil {
+			return nil, err
+		}
+	}
+	return s.httpsServerL, nil
+}
+
+func handleInterrupt(once *sync.Once, s *Server) {
+	once.Do(func() {
+		for range s.interrupt {
+			if s.interrupted {
+				s.Logf("Server already shutting down")
+				continue
+			}
+			s.interrupted = true
+			s.Logf("Shutting down... ")
+			if err := s.Shutdown(); err != nil {
+				s.Logf("HTTP server Shutdown: %v", err)
+			}
+		}
+	})
+}
+
+func signalNotify(interrupt chan<- os.Signal) {
+	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
+}

+ 66 - 0
swagger.yml

@@ -0,0 +1,66 @@
+consumes:
+- application/eu.reverservices.gem.spaas.v1+json
+info:
+  description: This API exposes an algorithm that computes optimal power production
+    allocation for a given power need and power plant capacity.
+  title: Optimal power production allocation service
+  version: 0.0.1
+paths:
+  /productionplan:
+    post:
+      summary: compute production plan
+      consumes: 
+        - application/json
+      operationId: ProductionPlan
+      parameters:
+        - in: body
+          name: payload format
+          schema: 
+            $ref: "#/definitions/payload"
+      description: |
+        Compute the optimal production plan for given load and capacity.
+      produces:
+        - application/json
+      responses:
+        200:
+          description: returns optimal production plan
+          schema:
+            type: array
+            items: 
+              type: object
+        default:
+          description: generic error response
+          schema:
+            $ref: "#/definitions/error"
+definitions:
+  payload:
+    type: object
+    required:
+      - load
+      - fuels
+      - powerplants
+    properties:
+      load:
+        type: number
+        format: float64
+      fuels:
+        type: object
+      powerplants:
+        type: array
+        items: 
+          type: object
+  error:
+    type: object
+    required:
+      - message
+    properties:
+      code:
+        type: integer
+        format: int64
+      message:
+        type: string
+produces:
+- application/eu.reverservices.gem.spaas.v1+json
+schemes:
+- http
+swagger: "2.0"