Skip to content

Commit

Permalink
Sync from server repo (14ee0894010)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Spilchen committed Dec 8, 2023
1 parent 4d7b04d commit d228f81
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 71 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ If you find a bug, [submit an issue](https://github.com/vertica/vcluster/issues)
with a complete and reproducible bug report.

For issues that are **not suitable** to be reported publicly on the GitHub
issue system (e.g. security related issues), report your issues to [Vertica
open source team](mailto:vertica-opensrc@microfocus.com) directly or file a
issue system (e.g., security related issues), report your issues to [Vertica
open source team](mailto:vertica-opensrc@opentext.com) directly or file a
case with Vertica support, if you have a support account.

# Feature Requests
Expand Down
60 changes: 51 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

[![Go Reference](https://pkg.go.dev/badge/github.com/vertica/vcluster.svg)](https://pkg.go.dev/github.com/vertica/vcluster)

This repository contains the Go library and command-line interface (CLI) to
administer a Vertica cluster with HTTP RESTful interfaces. These interfaces are
exposed by the following services:
This repository contains the vcluster-ops Go library and command-line interface to administer a Vertica cluster with a REST API. The REST API endpoints are exposed by the following services:
- Node Management Agent (NMA)
- Embedded HTTPS service

This CLI tool combines REST calls to provide a coherent Go interface to perform
administrator-level operations, including: creating a database, scaling
up/down, restarting the cluster, and stopping the cluster.
This CLI tool combines REST calls to provide a coherent Go interface so that you can perform the following administrator operations:
- Create a database
- Scale a cluster up and down
- Restart a cluster
- Stop a cluster
- Revive an Eon database

Traditionally, these operations were completed with
[admintools](https://docs.vertica.com/latest/en/admin/using-admin-tools/admin-tools-reference/writing-admin-tools-scripts/).
Historically, these operations were performed with [admintools](https://docs.vertica.com/latest/en/admin/using-admin-tools/admin-tools-reference/writing-admin-tools-scripts/).
However, admintools is not suitable for containerized environments because it
relies on SSH for communications and maintains a state file (admintools.conf)
on each Vertica host.
Expand All @@ -36,7 +36,8 @@ vcluster/
└── vclusterops
├── test_data
├── util
└── vlog
├── vlog
└── vstruct
```

- `/cmd/vcluster`: The `/cmd` directory contains executable code. The
Expand All @@ -56,3 +57,44 @@ directories in this project.
project and does not fit logically into an existing package.
- `/vclusterops/vlog`: Sets up a logging utility that writes to
`/opt/vertica/log/vcluster.log`.
- `/vclusterops/vstruct`: Contains helper structs used by vcluster-ops.


## Usage
Each source file in `vclusterops/` contains a `V<Operation>Options` struct with option fields that you can set for that operation, and a `V<Operation>OptionsFactory` factory function that returns a struct with sensible option defaults. General database and authentication options are available in `DatabaseOptions` in `vclusterops/vcluster_database_options.go`.

The following example imports the `vclusterops` library, and then calls functions from `vclusterops/create_db.go` to create a database:


```
import "github.com/vertica/vcluster/vclusterops"
// get default create_db options
opts := vclusterops.VCreateDatabaseOptionsFactory()
// set database options
opts.RawHosts = []string{"host1_ip", "host2_ip", "host3_ip"}
opts.DBName = "my_database"
*opts.ForceRemovalAtCreation = true
opts.CatalogPrefix = "/data"
opts.DataPrefix = "/data"
// set authentication options
opts.Key = "your_tls_key"
opts.Cert = "your_tls_cert"
opts.CaCert = "your_ca_cert"
*opts.UserName = "database_username"
opts.Password = "database_password"
// pass opts to VCreateDatabase function
vdb, err := vclusterops.VCreateDatabase(&opts)
if err != nil {
// handle the error here
}
```

We can use similar way to set up and call other vcluster-ops commands.


## Licensing
vcluster is open source code and is under the Apache 2.0 license. Please see `LICENSE` for details.
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ require (
github.com/tonglil/buflogr v1.0.1
go.uber.org/zap v1.25.0
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
golang.org/x/sys v0.8.0
golang.org/x/sys v0.15.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.26.2
k8s.io/client-go v0.26.2
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
Expand All @@ -35,10 +35,10 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
Expand Down
20 changes: 12 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand All @@ -13,6 +15,8 @@ github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpO
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -139,8 +143,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
Expand All @@ -157,16 +161,16 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
19 changes: 10 additions & 9 deletions vclusterops/cluster_op_engine_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ package vclusterops
import "github.com/vertica/vcluster/vclusterops/vlog"

type opEngineExecContext struct {
dispatcher requestDispatcher
networkProfiles map[string]networkProfile
nmaVDatabase nmaVDatabase
upHosts []string // a sorted host list that contains all up nodes
nodesInfo []NodeInfo
defaultSCName string // store the default subcluster name of the database
hostsWithLatestCatalog []string
startupCommandMap map[string][]string // store start up command map to restart nodes
dbInfo string // store the db info that retrieved from communal storage
dispatcher requestDispatcher
networkProfiles map[string]networkProfile
nmaVDatabase nmaVDatabase
upHosts []string // a sorted host list that contains all up nodes
nodesInfo []NodeInfo
defaultSCName string // store the default subcluster name of the database
hostsWithLatestCatalog []string
primaryHostsWithLatestCatalog []string
startupCommandMap map[string][]string // store start up command map to restart nodes
dbInfo string // store the db info that retrieved from communal storage
}

func makeOpEngineExecContext(logger vlog.Printer) opEngineExecContext {
Expand Down
23 changes: 23 additions & 0 deletions vclusterops/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"path"
"strings"

mapset "github.com/deckarep/golang-set/v2"
"github.com/vertica/vcluster/vclusterops/util"
"github.com/vertica/vcluster/vclusterops/vlog"
)
Expand Down Expand Up @@ -73,6 +74,28 @@ func updateCatalogPathMapFromCatalogEditor(hosts []string, nmaVDB *nmaVDatabase,
return nil
}

// Get primary nodes with latest catalog from catalog editor if the primaryHostsWithLatestCatalog info doesn't exist in execContext
func getPrimaryHostsWithLatestCatalog(nmaVDB *nmaVDatabase, hostsWithLatestCatalog []string, execContext *opEngineExecContext) []string {
if len(execContext.primaryHostsWithLatestCatalog) > 0 {
return execContext.primaryHostsWithLatestCatalog
}
emptyPrimaryHostsString := []string{}
primaryHostsSet := mapset.NewSet[string]()
for host, vnode := range nmaVDB.HostNodeMap {
if vnode.IsPrimary {
primaryHostsSet.Add(host)
}
}
hostsWithLatestCatalogSet := mapset.NewSet(hostsWithLatestCatalog...)
primaryHostsWithLatestCatalog := hostsWithLatestCatalogSet.Intersect(primaryHostsSet)
primaryHostsWithLatestCatalogList := primaryHostsWithLatestCatalog.ToSlice()
if len(primaryHostsWithLatestCatalogList) == 0 {
return emptyPrimaryHostsString
}
execContext.primaryHostsWithLatestCatalog = primaryHostsWithLatestCatalogList // save the primaryHostsWithLatestCatalog to execContext
return primaryHostsWithLatestCatalogList
}

// The following structs will store hosts' necessary information for https_get_up_nodes_op,
// https_get_nodes_information_from_running_db, and incoming operations.
type nodeStateInfo struct {
Expand Down
16 changes: 16 additions & 0 deletions vclusterops/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ func TestForupdateCatalogPathMapFromCatalogEditorNegative(t *testing.T) {
assert.ErrorContains(t, err, "fail to get host with highest catalog version")
}

func TestForGetPrimaryHostsWithLatestCatalog(t *testing.T) {
// prepare data for nmaVDB
mockNmaVNode1 := &nmaVNode{CatalogPath: "/data/test_db/v_test_db_node0001_catalog/Catalog", Address: "192.168.1.101", IsPrimary: false}
mockNmaVNode2 := &nmaVNode{CatalogPath: "/data/test_db/v_test_db_node0002_catalog/Catalog", Address: "192.168.1.102", IsPrimary: true}
mockHostNodeMap := map[string]*nmaVNode{"192.168.1.101": mockNmaVNode1, "192.168.1.102": mockNmaVNode2}
mockNmaVDB := &nmaVDatabase{HostNodeMap: mockHostNodeMap}
hostsWithLatestCatalog := []string{"192.168.1.101", "192.168.1.102", "192.168.1.104"}
// successfully get a primary host with latest catalog
primaryHostsWithLatestCatalog := getPrimaryHostsWithLatestCatalog(mockNmaVDB, hostsWithLatestCatalog, &opEngineExecContext{})
assert.Equal(t, primaryHostsWithLatestCatalog, []string{"192.168.1.102"})
// Unable to find any primary hosts with the latest catalog
hostsWithLatestCatalog = []string{}
primaryHostsWithLatestCatalog = getPrimaryHostsWithLatestCatalog(mockNmaVDB, hostsWithLatestCatalog, &opEngineExecContext{})
assert.Equal(t, primaryHostsWithLatestCatalog, []string{})
}

func TestForgetInitiatorHost(t *testing.T) {
nodesList1 := []string{"10.0.0.0", "10.0.0.1", "10.0.0.2"}
hostsToSkip1 := []string{"10.0.0.10", "10.0.0.11"}
Expand Down
52 changes: 33 additions & 19 deletions vclusterops/http_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func makeHTTPDownloadAdapter(logger vlog.Printer,
}

type responseBodyHandler interface {
readResponseBody(resp *http.Response) (string, error)
processResponseBody(resp *http.Response) (string, error)
}

// empty struct for default behavior of reading response body into memory
Expand Down Expand Up @@ -156,37 +156,35 @@ func (adapter *httpAdapter) sendRequest(request *hostHTTPRequest, resultChannel
}

func (adapter *httpAdapter) generateResult(resp *http.Response) hostHTTPResult {
bodyString, err := adapter.respBodyHandler.readResponseBody(resp)
bodyString, err := adapter.respBodyHandler.processResponseBody(resp)
if err != nil {
return adapter.makeExceptionResult(err)
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
if isSuccess(resp) {
return adapter.makeSuccessResult(bodyString, resp.StatusCode)
}
return adapter.makeFailResult(resp.Header, bodyString, resp.StatusCode)
}

func (reader *responseBodyReader) readResponseBody(resp *http.Response) (bodyString string, err error) {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("fail to read the response body: %w", err)
return "", err
}
bodyString = string(bodyBytes)

return bodyString, nil
func (*responseBodyReader) processResponseBody(resp *http.Response) (bodyString string, err error) {
return readResponseBody(resp)
}

func (downloader *responseBodyDownloader) readResponseBody(resp *http.Response) (bodyString string, err error) {
bytesWritten, err := downloader.downloadFile(resp)
if err != nil {
err = fmt.Errorf("fail to stream the response body to file %s: %w", downloader.destFilePath, err)
} else {
downloader.logger.Info("File downloaded", "File", downloader.destFilePath, "Bytes", bytesWritten)
func (downloader *responseBodyDownloader) processResponseBody(resp *http.Response) (bodyString string, err error) {
if isSuccess(resp) {
bytesWritten, err := downloader.downloadFile(resp)
if err != nil {
err = fmt.Errorf("fail to stream the response body to file %s: %w", downloader.destFilePath, err)
} else {
downloader.logger.Info("File downloaded", "File", downloader.destFilePath, "Bytes", bytesWritten)
}
return "", err
}
return "", err
// in case of error, we get an RFC7807 error, not a file
return readResponseBody(resp)
}

// downloadFile uses buffered read/writes to download the http response body to a file
func (downloader *responseBodyDownloader) downloadFile(resp *http.Response) (bytesWritten int64, err error) {
file, err := os.Create(downloader.destFilePath)
if err != nil {
Expand All @@ -196,6 +194,22 @@ func (downloader *responseBodyDownloader) downloadFile(resp *http.Response) (byt
return io.Copy(file, resp.Body)
}

// readResponseBody attempts to read the entire contents of the http response into bodyString
func readResponseBody(resp *http.Response) (bodyString string, err error) {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("fail to read the response body: %w", err)
return "", err
}
bodyString = string(bodyBytes)

return bodyString, nil
}

func isSuccess(resp *http.Response) bool {
return resp.StatusCode >= 200 && resp.StatusCode < 300
}

// makeSuccessResult is a factory method for hostHTTPResult when a success
// response comes back from a REST endpoints.
func (adapter *httpAdapter) makeSuccessResult(content string, statusCode int) hostHTTPResult {
Expand Down
31 changes: 29 additions & 2 deletions vclusterops/http_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ func TestHandleSuccessResponseCodes(t *testing.T) {

func TestHandleRFC7807Response(t *testing.T) {
adapter := httpAdapter{respBodyHandler: &responseBodyReader{}}
detail := "Cannot access communal storage"
rfcErr := rfc7807.New(rfc7807.CommunalAccessError).
WithDetail("Cannot access communal storage")
WithDetail(detail)
b, err := json.Marshal(rfcErr)
assert.Equal(t, err, nil)
mockBodyReader := MockReadCloser{
Expand All @@ -165,7 +166,33 @@ func TestHandleRFC7807Response(t *testing.T) {
ok := errors.As(result.err, &problem)
assert.True(t, ok)
assert.Equal(t, 500, problem.Status)
assert.Equal(t, "Cannot access communal storage", problem.Detail)
assert.Equal(t, detail, problem.Detail)
}

func TestHandleFileDownloadErrorResponse(t *testing.T) {
adapter := httpAdapter{respBodyHandler: &responseBodyDownloader{destFilePath: "/never/use/me"}}
detail := "Something went horribly wrong and this is not a file"
rfcErr := rfc7807.New(rfc7807.GenericHTTPInternalServerError).
WithDetail(detail)
b, err := json.Marshal(rfcErr)
assert.Equal(t, err, nil)
mockBodyReader := MockReadCloser{
body: b,
}
mockResp := &http.Response{
StatusCode: rfcErr.Status,
Header: http.Header{},
Body: &mockBodyReader,
}
mockResp.Header.Add("Content-Type", rfc7807.ContentType)
result := adapter.generateResult(mockResp)
assert.Equal(t, result.status, FAILURE)
assert.NotEqual(t, result.err, nil)
problem := &rfc7807.VProblem{}
ok := errors.As(result.err, &problem)
assert.True(t, ok)
assert.Equal(t, 500, problem.Status)
assert.Equal(t, detail, problem.Detail)
}

func TestHandleGenericErrorResponse(t *testing.T) {
Expand Down
Loading

0 comments on commit d228f81

Please sign in to comment.