From 9d5f04231fdda92ac190649743eb1a5af552c83b Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Mon, 10 Jun 2024 13:39:05 +0100 Subject: [PATCH] Support External Networks Damnit CAPO!!!! It does infer the external network, and when I say "the" I mean only one, or it has a hissy fit, so we need to arbitrarily choose one for it. --- charts/region/Chart.yaml | 4 +- pkg/handler/handler.go | 48 +++++++++- pkg/openapi/client.go | 141 ++++++++++++++++++++++++++++ pkg/openapi/router.go | 39 ++++++++ pkg/openapi/schema.go | 137 ++++++++++++++------------- pkg/openapi/server.spec.yaml | 50 ++++++++++ pkg/openapi/types.go | 15 +++ pkg/providers/interfaces.go | 3 + pkg/providers/openstack/network.go | 16 ---- pkg/providers/openstack/provider.go | 27 +++++- pkg/providers/types.go | 11 +++ 11 files changed, 404 insertions(+), 87 deletions(-) diff --git a/charts/region/Chart.yaml b/charts/region/Chart.yaml index 1e7ade7..47adfa8 100644 --- a/charts/region/Chart.yaml +++ b/charts/region/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn's Region Controller type: application -version: v0.1.2 -appVersion: v0.1.2 +version: v0.1.3 +appVersion: v0.1.3 icon: https://raw.githubusercontent.com/unikorn-cloud/unikorn/main/icons/default.png diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 8d2689e..feb9f87 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -220,6 +220,16 @@ func convertCloudConfig(identity *unikornv1.Identity, in *providers.CloudConfig) return out } +func generateClusterInfo(in *openapi.IdentityWrite) *providers.ClusterInfo { + out := &providers.ClusterInfo{ + OrganizationID: in.OrganizationId, + ProjectID: in.ProjectId, + ClusterID: in.ClusterId, + } + + return out +} + func (h *Handler) PostApiV1RegionsRegionIDIdentities(w http.ResponseWriter, r *http.Request, regionID openapi.RegionIDParameter) { request := &openapi.IdentityWrite{} @@ -234,7 +244,7 @@ func (h *Handler) PostApiV1RegionsRegionIDIdentities(w http.ResponseWriter, r *h return } - identity, cloudconfig, err := provider.CreateIdentity(r.Context(), &providers.ClusterInfo{}) + identity, cloudconfig, err := provider.CreateIdentity(r.Context(), generateClusterInfo(request)) if err != nil { errors.HandleError(w, r, err) return @@ -246,3 +256,39 @@ func (h *Handler) PostApiV1RegionsRegionIDIdentities(w http.ResponseWriter, r *h func (h *Handler) DeleteApiV1RegionsRegionIDIdentitiesIdentityID(w http.ResponseWriter, r *http.Request, regionID openapi.RegionIDParameter, identityID openapi.IdentityIDParameter) { } + +func convertExternalNetwork(in providers.ExternalNetwork) openapi.ExternalNetwork { + out := openapi.ExternalNetwork{ + Id: in.ID, + Name: in.Name, + } + + return out +} + +func convertExternalNetworks(in providers.ExternalNetworks) openapi.ExternalNetworks { + out := make(openapi.ExternalNetworks, len(in)) + + for i := range in { + out[i] = convertExternalNetwork(in[i]) + } + + return out +} + +func (h *Handler) GetApiV1RegionsRegionIDExternalnetworks(w http.ResponseWriter, r *http.Request, regionID openapi.RegionIDParameter) { + provider, err := region.NewClient(h.client, h.namespace).Provider(r.Context(), regionID) + if err != nil { + errors.HandleError(w, r, err) + return + } + + result, err := provider.ListExternalNetworks(r.Context()) + if err != nil { + errors.HandleError(w, r, err) + return + } + + h.setCacheable(w) + util.WriteJSONResponse(w, r, http.StatusOK, convertExternalNetworks(result)) +} diff --git a/pkg/openapi/client.go b/pkg/openapi/client.go index 01ca67c..59edd9d 100644 --- a/pkg/openapi/client.go +++ b/pkg/openapi/client.go @@ -93,6 +93,9 @@ type ClientInterface interface { // GetApiV1Regions request GetApiV1Regions(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiV1RegionsRegionIDExternalnetworks request + GetApiV1RegionsRegionIDExternalnetworks(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiV1RegionsRegionIDFlavors request GetApiV1RegionsRegionIDFlavors(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -120,6 +123,18 @@ func (c *Client) GetApiV1Regions(ctx context.Context, reqEditors ...RequestEdito return c.Client.Do(req) } +func (c *Client) GetApiV1RegionsRegionIDExternalnetworks(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetApiV1RegionsRegionIDExternalnetworksRequest(c.Server, regionID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetApiV1RegionsRegionIDFlavors(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiV1RegionsRegionIDFlavorsRequest(c.Server, regionID) if err != nil { @@ -207,6 +222,40 @@ func NewGetApiV1RegionsRequest(server string) (*http.Request, error) { return req, nil } +// NewGetApiV1RegionsRegionIDExternalnetworksRequest generates requests for GetApiV1RegionsRegionIDExternalnetworks +func NewGetApiV1RegionsRegionIDExternalnetworksRequest(server string, regionID RegionIDParameter) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "regionID", runtime.ParamLocationPath, regionID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/regions/%s/externalnetworks", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetApiV1RegionsRegionIDFlavorsRequest generates requests for GetApiV1RegionsRegionIDFlavors func NewGetApiV1RegionsRegionIDFlavorsRequest(server string, regionID RegionIDParameter) (*http.Request, error) { var err error @@ -409,6 +458,9 @@ type ClientWithResponsesInterface interface { // GetApiV1RegionsWithResponse request GetApiV1RegionsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1RegionsResponse, error) + // GetApiV1RegionsRegionIDExternalnetworksWithResponse request + GetApiV1RegionsRegionIDExternalnetworksWithResponse(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionIDExternalnetworksResponse, error) + // GetApiV1RegionsRegionIDFlavorsWithResponse request GetApiV1RegionsRegionIDFlavorsWithResponse(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionIDFlavorsResponse, error) @@ -448,6 +500,32 @@ func (r GetApiV1RegionsResponse) StatusCode() int { return 0 } +type GetApiV1RegionsRegionIDExternalnetworksResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ExternalNetworksResponse + JSON401 *externalRef0.UnauthorizedResponse + JSON403 *externalRef0.ForbiddenResponse + JSON404 *externalRef0.NotFoundResponse + JSON500 *externalRef0.InternalServerErrorResponse +} + +// Status returns HTTPResponse.Status +func (r GetApiV1RegionsRegionIDExternalnetworksResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetApiV1RegionsRegionIDExternalnetworksResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetApiV1RegionsRegionIDFlavorsResponse struct { Body []byte HTTPResponse *http.Response @@ -558,6 +636,15 @@ func (c *ClientWithResponses) GetApiV1RegionsWithResponse(ctx context.Context, r return ParseGetApiV1RegionsResponse(rsp) } +// GetApiV1RegionsRegionIDExternalnetworksWithResponse request returning *GetApiV1RegionsRegionIDExternalnetworksResponse +func (c *ClientWithResponses) GetApiV1RegionsRegionIDExternalnetworksWithResponse(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionIDExternalnetworksResponse, error) { + rsp, err := c.GetApiV1RegionsRegionIDExternalnetworks(ctx, regionID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetApiV1RegionsRegionIDExternalnetworksResponse(rsp) +} + // GetApiV1RegionsRegionIDFlavorsWithResponse request returning *GetApiV1RegionsRegionIDFlavorsResponse func (c *ClientWithResponses) GetApiV1RegionsRegionIDFlavorsWithResponse(ctx context.Context, regionID RegionIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionIDFlavorsResponse, error) { rsp, err := c.GetApiV1RegionsRegionIDFlavors(ctx, regionID, reqEditors...) @@ -642,6 +729,60 @@ func ParseGetApiV1RegionsResponse(rsp *http.Response) (*GetApiV1RegionsResponse, return response, nil } +// ParseGetApiV1RegionsRegionIDExternalnetworksResponse parses an HTTP response from a GetApiV1RegionsRegionIDExternalnetworksWithResponse call +func ParseGetApiV1RegionsRegionIDExternalnetworksResponse(rsp *http.Response) (*GetApiV1RegionsRegionIDExternalnetworksResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetApiV1RegionsRegionIDExternalnetworksResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ExternalNetworksResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest externalRef0.UnauthorizedResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest externalRef0.ForbiddenResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest externalRef0.NotFoundResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.InternalServerErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetApiV1RegionsRegionIDFlavorsResponse parses an HTTP response from a GetApiV1RegionsRegionIDFlavorsWithResponse call func ParseGetApiV1RegionsRegionIDFlavorsResponse(rsp *http.Response) (*GetApiV1RegionsRegionIDFlavorsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/openapi/router.go b/pkg/openapi/router.go index 4e2157a..9b4f399 100644 --- a/pkg/openapi/router.go +++ b/pkg/openapi/router.go @@ -18,6 +18,9 @@ type ServerInterface interface { // (GET /api/v1/regions) GetApiV1Regions(w http.ResponseWriter, r *http.Request) + // (GET /api/v1/regions/{regionID}/externalnetworks) + GetApiV1RegionsRegionIDExternalnetworks(w http.ResponseWriter, r *http.Request, regionID RegionIDParameter) + // (GET /api/v1/regions/{regionID}/flavors) GetApiV1RegionsRegionIDFlavors(w http.ResponseWriter, r *http.Request, regionID RegionIDParameter) @@ -40,6 +43,11 @@ func (_ Unimplemented) GetApiV1Regions(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// (GET /api/v1/regions/{regionID}/externalnetworks) +func (_ Unimplemented) GetApiV1RegionsRegionIDExternalnetworks(w http.ResponseWriter, r *http.Request, regionID RegionIDParameter) { + w.WriteHeader(http.StatusNotImplemented) +} + // (GET /api/v1/regions/{regionID}/flavors) func (_ Unimplemented) GetApiV1RegionsRegionIDFlavors(w http.ResponseWriter, r *http.Request, regionID RegionIDParameter) { w.WriteHeader(http.StatusNotImplemented) @@ -86,6 +94,34 @@ func (siw *ServerInterfaceWrapper) GetApiV1Regions(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetApiV1RegionsRegionIDExternalnetworks operation middleware +func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionIDExternalnetworks(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "regionID" ------------- + var regionID RegionIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "regionID", runtime.ParamLocationPath, chi.URLParam(r, "regionID"), ®ionID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "regionID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetApiV1RegionsRegionIDExternalnetworks(w, r, regionID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetApiV1RegionsRegionIDFlavors operation middleware func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionIDFlavors(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -323,6 +359,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/regions", wrapper.GetApiV1Regions) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/regions/{regionID}/externalnetworks", wrapper.GetApiV1RegionsRegionIDExternalnetworks) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/regions/{regionID}/flavors", wrapper.GetApiV1RegionsRegionIDFlavors) }) diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index 1d73666..e1c40dc 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -19,72 +19,77 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index b1f5a2c..93abecb 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -108,6 +108,29 @@ paths: $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/notFoundResponse' '500': $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/internalServerErrorResponse' + /api/v1/regions/{regionID}/externalnetworks: + description: |- + Allows access to "external networks" for providers that support them. + While CAPO can infer the external network itself, it craps out - + as per usual - if more than one is found, wracked with indescision. + Thus we still need to handle it as a special snowflake. + parameters: + - $ref: '#/components/parameters/regionIDParameter' + get: + description: Get a list of external networks. + security: + - oauth2Authentication: [] + responses: + '200': + $ref: '#/components/responses/externalNetworksResponse' + '401': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/unauthorizedResponse' + '403': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/forbiddenResponse' + '404': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/notFoundResponse' + '500': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/internalServerErrorResponse' components: parameters: regionIDParameter: @@ -322,6 +345,24 @@ components: $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/schemas/projectScopedResourceReadMetadata' spec: $ref: '#/components/schemas/identitySpec' + externalNetwork: + description: An Openstack external network. + type: object + required: + - id + - name + properties: + id: + description: The resource ID. + type: string + name: + description: The resource name. + type: string + externalNetworks: + description: A list of openstack external networks. + type: array + items: + $ref: '#/components/schemas/externalNetwork' requestBodies: identityRequest: description: A request for an identity. @@ -407,6 +448,15 @@ components: cloudConfig: dGhpcyBpcyBhIHRlc3QK projectId: eb9c92d937464d14bf87e50fa726380d userId: a19678a28126497dba24b54c96a064fa + externalNetworksResponse: + description: A list of valid external networks. + content: + application/json: + schema: + $ref: '#/components/schemas/externalNetworks' + example: + - id: c3f92716-1fdc-4b7b-906e-4fe0154dfcdf + name: foo securitySchemes: oauth2Authentication: description: Operation requires OAuth2 bearer token authentication. diff --git a/pkg/openapi/types.go b/pkg/openapi/types.go index 61f46d0..def79d4 100644 --- a/pkg/openapi/types.go +++ b/pkg/openapi/types.go @@ -22,6 +22,18 @@ const ( Openstack RegionType = "openstack" ) +// ExternalNetwork An Openstack external network. +type ExternalNetwork struct { + // Id The resource ID. + Id string `json:"id"` + + // Name The resource name. + Name string `json:"name"` +} + +// ExternalNetworks A list of openstack external networks. +type ExternalNetworks = []ExternalNetwork + // Flavor A flavor. type Flavor struct { // Metadata This metadata is for resources that just exist, and don't require @@ -182,6 +194,9 @@ type IdentityIDParameter = KubernetesNameParameter // RegionIDParameter A Kubernetes name. Must be a valid DNS containing only lower case characters, numbers or hyphens, start and end with a character or number, and be at most 63 characters in length. type RegionIDParameter = KubernetesNameParameter +// ExternalNetworksResponse A list of openstack external networks. +type ExternalNetworksResponse = ExternalNetworks + // FlavorsResponse A list of flavors. type FlavorsResponse = Flavors diff --git a/pkg/providers/interfaces.go b/pkg/providers/interfaces.go index 3e31357..d179100 100644 --- a/pkg/providers/interfaces.go +++ b/pkg/providers/interfaces.go @@ -32,4 +32,7 @@ type Provider interface { Images(ctx context.Context) (ImageList, error) // CreateIdentity creates a new identity for cloud infrastructure. CreateIdentity(ctx context.Context, info *ClusterInfo) (*unikornv1.Identity, *CloudConfig, error) + // ListExternalNetworks returns a list of external networks if the platform + // supports such a concept. + ListExternalNetworks(ctx context.Context) (ExternalNetworks, error) } diff --git a/pkg/providers/openstack/network.go b/pkg/providers/openstack/network.go index 480abb0..ca40489 100644 --- a/pkg/providers/openstack/network.go +++ b/pkg/providers/openstack/network.go @@ -87,19 +87,3 @@ func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Networ return results, nil } - -/* -// Get a network for external connectivity. -func (c *NetworkClient) defaultExternalNetwork(ctx context.Context) (*networks.Network, error) { - externalNetworks, err := c.ExternalNetworks(ctx) - if err != nil { - return nil, err - } - - if len(externalNetworks) == 0 { - return nil, fmt.Errorf("%w: default external network", ErrResourceNotFound) - } - - return &externalNetworks[0], nil -} -*/ diff --git a/pkg/providers/openstack/provider.go b/pkg/providers/openstack/provider.go index a0b9053..c32259e 100644 --- a/pkg/providers/openstack/provider.go +++ b/pkg/providers/openstack/provider.go @@ -212,7 +212,6 @@ func (p *Provider) image(ctx context.Context) (*ImageClient, error) { return p._image, nil } -/* func (p *Provider) network(ctx context.Context) (*NetworkClient, error) { p.lock.Lock() defer p.lock.Unlock() @@ -223,7 +222,6 @@ func (p *Provider) network(ctx context.Context) (*NetworkClient, error) { return p._network, nil } -*/ // Flavors list all available flavors. func (p *Provider) Flavors(ctx context.Context) (providers.FlavorList, error) { @@ -548,3 +546,28 @@ func (p *Provider) DeconfigureCluster(ctx context.Context, state *providers.Open return nil } + +// ListExternalNetworks returns a list of external networks if the platform +// supports such a concept. +func (p *Provider) ListExternalNetworks(ctx context.Context) (providers.ExternalNetworks, error) { + networkService, err := p.network(ctx) + if err != nil { + return nil, err + } + + result, err := networkService.ExternalNetworks(ctx) + if err != nil { + return nil, err + } + + out := make(providers.ExternalNetworks, len(result)) + + for i, in := range result { + out[i] = providers.ExternalNetwork{ + ID: in.ID, + Name: in.Name, + } + } + + return out, nil +} diff --git a/pkg/providers/types.go b/pkg/providers/types.go index 0561377..59c0aef 100644 --- a/pkg/providers/types.go +++ b/pkg/providers/types.go @@ -128,3 +128,14 @@ type CloudConfig struct { // OpenStack is populated when the type is "openstack" OpenStack *OpenStackCloudConfig } + +// ExternalNetwork represents an external network. +type ExternalNetwork struct { + // ID is the provider specific netwokr ID. + ID string + // Name is the netwokr name. + Name string +} + +// ExternalNetworks is a list of provider networks. +type ExternalNetworks []ExternalNetwork