Skip to content

Commit

Permalink
Merge pull request #1251 from stgraber/ovn
Browse files Browse the repository at this point in the history
Add OVN load balancer state API
  • Loading branch information
hallyn committed Sep 25, 2024
2 parents 9775c31 + b01cd77 commit c6f6b22
Show file tree
Hide file tree
Showing 26 changed files with 2,155 additions and 1,498 deletions.
19 changes: 19 additions & 0 deletions client/incus_network_load_balancers.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,22 @@ func (r *ProtocolIncus) DeleteNetworkLoadBalancer(networkName string, listenAddr

return nil
}

// GetNetworkLoadBalancerState returns a Network load balancer state for the provided network and listen address.
func (r *ProtocolIncus) GetNetworkLoadBalancerState(networkName string, listenAddress string) (*api.NetworkLoadBalancerState, error) {
err := r.CheckExtension("network_load_balancer_state")
if err != nil {
return nil, err
}

lbState := api.NetworkLoadBalancerState{}

// Fetch the raw value.
u := api.NewURL().Path("networks", networkName, "load-balancers", listenAddress, "state")
_, err = r.queryStruct("GET", u.String(), nil, "", &lbState)
if err != nil {
return nil, err
}

return &lbState, nil
}
1 change: 1 addition & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type InstanceServer interface {
CreateNetworkLoadBalancer(networkName string, forward api.NetworkLoadBalancersPost) error
UpdateNetworkLoadBalancer(networkName string, listenAddress string, forward api.NetworkLoadBalancerPut, ETag string) (err error)
DeleteNetworkLoadBalancer(networkName string, listenAddress string) (err error)
GetNetworkLoadBalancerState(networkName string, listenAddress string) (lbState *api.NetworkLoadBalancerState, err error)

// Network peer functions ("network_peer" API extension)
GetNetworkPeerNames(networkName string) ([]string, error)
Expand Down
75 changes: 75 additions & 0 deletions cmd/incus/network_load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func (c *cmdNetworkLoadBalancer) Command() *cobra.Command {
networkLoadBalancerGetCmd := cmdNetworkLoadBalancerGet{global: c.global, networkLoadBalancer: c}
cmd.AddCommand(networkLoadBalancerGetCmd.Command())

// Info.
networkLoadBalancerInfoCmd := cmdNetworkLoadBalancerInfo{global: c.global, networkLoadBalancer: c}
cmd.AddCommand(networkLoadBalancerInfoCmd.Command())

// Set.
networkLoadBalancerSetCmd := cmdNetworkLoadBalancerSet{global: c.global, networkLoadBalancer: c}
cmd.AddCommand(networkLoadBalancerSetCmd.Command())
Expand Down Expand Up @@ -1258,3 +1262,74 @@ func (c *cmdNetworkLoadBalancerPort) RunRemove(cmd *cobra.Command, args []string

return client.UpdateNetworkLoadBalancer(resource.name, loadBalancer.ListenAddress, loadBalancer.Writable(), etag)
}

// Info.
type cmdNetworkLoadBalancerInfo struct {
global *cmdGlobal
networkLoadBalancer *cmdNetworkLoadBalancer
}

// Command generates the command definition.
func (c *cmdNetworkLoadBalancerInfo) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = usage("info", i18n.G("[<remote>:]<network> <listen_address>"))
cmd.Short = i18n.G("Get current load balancer status")
cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G("Get current load-balacner status"))
cmd.RunE = c.Run

return cmd
}

// Run runs the actual command logic.
func (c *cmdNetworkLoadBalancerInfo) Run(cmd *cobra.Command, args []string) error {
// Quick checks.
exit, err := c.global.CheckArgs(cmd, args, 2, 2)
if exit {
return err
}

// Parse remote
resources, err := c.global.ParseServers(args[0])
if err != nil {
return err
}

resource := resources[0]
client := resource.server

if resource.name == "" {
return fmt.Errorf("%s", i18n.G("Missing network name"))
}

if args[1] == "" {
return fmt.Errorf("%s", i18n.G("Missing listen address"))
}

// Get the load-balancer state.
lbState, err := client.GetNetworkLoadBalancerState(resource.name, args[1])
if err != nil {
return err
}

// Render the state.
if lbState.BackendHealth == nil {
// Currently the only field in the state endpoint is the backend health, fail if it's missing.
return fmt.Errorf("%s", i18n.G("No load-balancer health information available"))
}

fmt.Println(i18n.G("Backend health:"))
for backend, info := range lbState.BackendHealth {
if len(info.Ports) == 0 {
continue
}

fmt.Printf(" %s (%s):\n", backend, info.Address)
for _, port := range info.Ports {
fmt.Printf(" - %s/%d: %s\n", port.Protocol, port.Port, port.Status)
}

fmt.Println("")
}

return nil
}
1 change: 1 addition & 0 deletions cmd/incusd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ var api10 = []APIEndpoint{
networkIntegrationCmd,
networkIntegrationsCmd,
networkLoadBalancerCmd,
networkLoadBalancerStateCmd,
networkLoadBalancersCmd,
networkPeerCmd,
networkPeersCmd,
Expand Down
101 changes: 101 additions & 0 deletions cmd/incusd/network_load_balancers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ var networkLoadBalancerCmd = APIEndpoint{
Patch: APIEndpointAction{Handler: networkLoadBalancerPut, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanEdit, "networkName")},
}

var networkLoadBalancerStateCmd = APIEndpoint{
Path: "networks/{networkName}/load-balancers/{listenAddress}/state",

Get: APIEndpointAction{Handler: networkLoadBalancerStateGet, AccessHandler: allowPermission(auth.ObjectTypeNetwork, auth.EntitlementCanView, "networkName")},
}

// API endpoints

// swagger:operation GET /1.0/networks/{networkName}/load-balancers network-load-balancers network_load_balancers_get
Expand Down Expand Up @@ -617,3 +623,98 @@ func networkLoadBalancerPut(d *Daemon, r *http.Request) response.Response {

return response.EmptySyncResponse
}

// swagger:operation GET /1.0/networks/{networkName}/load-balancers/{listenAddress}/state network-load-balancers network_load_balancer_state_get
//
// Get the network address load balancer state
//
// Get the current state of a specific network address load balancer.
//
// ---
// produces:
// - application/json
// parameters:
// - in: query
// name: project
// description: Project name
// type: string
// example: default
// responses:
// "200":
// description: Load Balancer state
// schema:
// type: object
// description: Sync response
// properties:
// type:
// type: string
// description: Response type
// example: sync
// status:
// type: string
// description: Status description
// example: Success
// status_code:
// type: integer
// description: Status code
// example: 200
// metadata:
// $ref: "#/definitions/NetworkLoadBalancerState"
// "403":
// $ref: "#/responses/Forbidden"
// "500":
// $ref: "#/responses/InternalServerError"
func networkLoadBalancerStateGet(d *Daemon, r *http.Request) response.Response {
s := d.State()

resp := forwardedResponseIfTargetIsRemote(s, r)
if resp != nil {
return resp
}

projectName, reqProject, err := project.NetworkProject(s.DB.Cluster, request.ProjectParam(r))
if err != nil {
return response.SmartError(err)
}

networkName, err := url.PathUnescape(mux.Vars(r)["networkName"])
if err != nil {
return response.SmartError(err)
}

n, err := network.LoadByName(s, projectName, networkName)
if err != nil {
return response.SmartError(fmt.Errorf("Failed loading network: %w", err))
}

// Check if project allows access to network.
if !project.NetworkAllowed(reqProject.Config, networkName, n.IsManaged()) {
return response.SmartError(api.StatusErrorf(http.StatusNotFound, "Network not found"))
}

if !n.Info().LoadBalancers {
return response.BadRequest(fmt.Errorf("Network driver %q does not support load balancers", n.Type()))
}

listenAddress, err := url.PathUnescape(mux.Vars(r)["listenAddress"])
if err != nil {
return response.SmartError(err)
}

var loadBalancer *api.NetworkLoadBalancer
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
_, loadBalancer, err = tx.GetNetworkLoadBalancer(ctx, n.ID(), false, listenAddress)

return err
})
if err != nil {
return response.SmartError(err)
}

lbState, err := n.LoadBalancerState(*loadBalancer)
if err != nil {
return response.SmartError(fmt.Errorf("Failed fetching load balancer state: %w", err))
}

return response.SyncResponse(true, lbState)
}
5 changes: 5 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2610,3 +2610,8 @@ This adds `none` as a value for `ipv4.address` and `ipv6.address` for OVN NICs.
## `instances_state_os_info`

This extension adds a pointer to an `InstanceStateOSInfo` struct to the instance's state API.

## `network_load_balancer_state`

This adds a new `/1.0/networks/NAME/load-balancers/IP/state` API endpoint
which returns load-balancer health check information (when configured).
78 changes: 78 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3246,6 +3246,44 @@ definitions:
x-go-name: Ports
type: object
x-go-package: github.com/lxc/incus/v6/shared/api
NetworkLoadBalancerState:
description: NetworkLoadBalancerState is used for showing current state of a load balancer
properties:
backend_health:
additionalProperties:
$ref: '#/definitions/NetworkLoadBalancerStateBackendHealth'
type: object
x-go-name: BackendHealth
type: object
x-go-package: github.com/lxc/incus/v6/shared/api
NetworkLoadBalancerStateBackendHealth:
description: NetworkLoadBalancerStateBackendHealth represents the health of a particular load-balancer backend
properties:
address:
type: string
x-go-name: Address
ports:
items:
$ref: '#/definitions/NetworkLoadBalancerStateBackendHealthPort'
type: array
x-go-name: Ports
type: object
x-go-package: github.com/lxc/incus/v6/shared/api
NetworkLoadBalancerStateBackendHealthPort:
properties:
port:
format: int64
type: integer
x-go-name: Port
protocol:
type: string
x-go-name: Protocol
status:
type: string
x-go-name: Status
title: NetworkLoadBalancerStateBackendHealthPort represents the health status of a particular load-balancer backend port.
type: object
x-go-package: github.com/lxc/incus/v6/shared/api
NetworkLoadBalancersPost:
description: NetworkLoadBalancersPost represents the fields of a new network load balancer
properties:
Expand Down Expand Up @@ -12487,6 +12525,46 @@ paths:
summary: Update the network address load balancer
tags:
- network-load-balancers
/1.0/networks/{networkName}/load-balancers/{listenAddress}/state:
get:
description: Get the current state of a specific network address load balancer.
operationId: network_load_balancer_state_get
parameters:
- description: Project name
example: default
in: query
name: project
type: string
produces:
- application/json
responses:
"200":
description: Load Balancer state
schema:
description: Sync response
properties:
metadata:
$ref: '#/definitions/NetworkLoadBalancerState'
status:
description: Status description
example: Success
type: string
status_code:
description: Status code
example: 200
type: integer
type:
description: Response type
example: sync
type: string
type: object
"403":
$ref: '#/responses/Forbidden'
"500":
$ref: '#/responses/InternalServerError'
summary: Get the network address load balancer state
tags:
- network-load-balancers
/1.0/networks/{networkName}/load-balancers?recursion=1:
get:
description: Returns a list of network address load balancers (structs).
Expand Down
5 changes: 5 additions & 0 deletions internal/server/network/driver_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,11 @@ func (n *common) LoadBalancerUpdate(listenAddress string, newLoadBalancer api.Ne
return ErrNotImplemented
}

// LoadBalancerState returns ErrNotImplemented for drivers that do not support load balancers..
func (n *common) LoadBalancerState(loadBalancer api.NetworkLoadBalancer) (*api.NetworkLoadBalancerState, error) {
return nil, ErrNotImplemented
}

// LoadBalancerDelete returns ErrNotImplemented for drivers that do not support load balancers..
func (n *common) LoadBalancerDelete(listenAddress string, clientType request.ClientType) error {
return ErrNotImplemented
Expand Down
Loading

0 comments on commit c6f6b22

Please sign in to comment.