Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: OpenAPI 3 parse and convert #2460

Merged
merged 23 commits into from
Jun 21, 2022

Conversation

bzp2010
Copy link
Contributor

@bzp2010 bzp2010 commented May 30, 2022

Please answer these questions before submitting a pull request, or your PR will get closed.

Why submit this pull request?

  • Bugfix
  • New feature provided
  • Improve performance
  • Backport patches

What changes will this PR take into?

Improved backend implementation of OpenAPI3 import to enable import of standard OpenAPI3 minimum use cases. In the current PR I will not do the integration work, this separate module will be checked by way of unit tests and in the next PR it will be integrated into the data import interface.

Checklist:

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

@codecov-commenter
Copy link

codecov-commenter commented Jun 1, 2022

Codecov Report

Merging #2460 (9d3ffd1) into master (5e624a5) will decrease coverage by 0.15%.
The diff coverage is 75.86%.

@@            Coverage Diff             @@
##           master    #2460      +/-   ##
==========================================
- Coverage   68.98%   68.82%   -0.16%     
==========================================
  Files         131      191      +60     
  Lines        3456     7547    +4091     
  Branches      845      842       -3     
==========================================
+ Hits         2384     5194    +2810     
- Misses       1072     2048     +976     
- Partials        0      305     +305     
Flag Coverage Δ
backend-e2e-test-ginkgo 59.87% <ø> (?)
backend-unit-test 50.34% <75.86%> (?)
frontend-e2e-test 68.90% <ø> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...rnal/handler/data_loader/loader/openapi3/export.go 0.00% <0.00%> (ø)
...rnal/handler/data_loader/loader/openapi3/import.go 78.57% <78.57%> (ø)
web/src/pages/Consumer/Create.tsx 76.92% <0.00%> (-5.77%) ⬇️
web/src/pages/Consumer/service.ts 90.00% <0.00%> (-2.31%) ⬇️
web/src/pages/Consumer/List.tsx 91.66% <0.00%> (ø)
web/src/components/RawDataEditor/RawDataEditor.tsx 38.96% <0.00%> (ø)
api/internal/handler/migrate/migrate.go 59.67% <0.00%> (ø)
api/internal/filter/cors.go 38.88% <0.00%> (ø)
api/cmd/version.go 57.14% <0.00%> (ø)
api/internal/utils/runtime/runtime.go 66.66% <0.00%> (ø)
... and 56 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5e624a5...9d3ffd1. Read the comment docs.

@bzp2010 bzp2010 self-assigned this Jun 2, 2022
@bzp2010 bzp2010 marked this pull request as ready for review June 2, 2022 07:52
Copy link
Contributor

@starsz starsz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any proposal on this PR?

@bzp2010
Copy link
Contributor Author

bzp2010 commented Jun 6, 2022

Do we have any proposal on this PR?

@starsz Yes, I was created a proposal for these works, you can see it here. It contains current problems and current solutions, from which you can learn why some of the content in this PR is implemented in this way and facilitate your comments.

#2465

@bzp2010 bzp2010 requested a review from starsz June 6, 2022 03:57
@bzp2010 bzp2010 changed the title refactor: OpenAPI 3 import refactor: OpenAPI 3 parse and convert Jun 6, 2022
// replace parameter in uri to wildcard
realUri := regURIVar.ReplaceAllString(uri, "*")
// generate route name
routeID := o.TaskName + "_" + strings.NewReplacer("/", "-", "{", "", "}", "").Replace(strings.TrimPrefix(uri, "/"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use base64 or hash to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, Thanks for your reminder.

What do you think about using the base64 or hashed content as ID and the original uri as Name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe base64 is better. That's more easily to debug (we can decode from the id to get the info)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After testing, using URI + base64 will generate some very long IDs, which may exceed the schema check limit of APISIX on the one hand, and confuse the format of ID display in the route list on the other. The symbols in base64 will also trigger the schema ID check of APISIX, while for Name only the length is required and the content is completely customizable.

image

So I took the following approach, instead of generating IDs, which are automatically generated by the dashboard storage layer, we store the information in the route name, which has this format TaskName_URI[_Method]. For a route with a URI of /consumer/{cid}, the method is GET and POST, it generates this TaskName_consumer/{cid}[_GET/POST]

taskName: test

methodMerge: true
Method + URI:
GET|POST /consumer/{cid} => test_consumer/{cid}

methodMerge: false
Method + URI:
GET|POST /consumer/{cid} => test_consumer/{cid}_GET  +  test_consumer/{cid}_POST

},
}

u, err := url.Parse(servers[0].URL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only parse servers[0] ?

Copy link
Contributor Author

@bzp2010 bzp2010 Jun 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is based on the following considerations:

  • The first servers are usually the node that is logically "used first".
  • Different servers may have different global paths
What's "global paths"?

servers:
- url: https://z.com/test/
- url: https://x.com/non-test/
- url: https://c.com/test-now/

According to the principle of APISIX, we need to set /test, /non-test,
/test-now, respectively, as the prefix of the route uri of APISIX,
for example, set the original /user/* to /test/user/*, at this point
we can't operate.

Do you have any better suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create different routes for each server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is appropriate, it generates a lot of useless routes. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the other routes are useless?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @bzp2010, please check this, it affects the overall convert

Copy link
Contributor Author

@bzp2010 bzp2010 Jun 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little confused about servers, should they be the URL exposed by the gateway or the URL of the API backend service, please confirm, thanks.

@nic-chen We can look at it this way, during API debugging it is the address actually requested by the debugger, so it should be the address provided by APISIX to the outside world, and the original value should be upstream.
So in effect, the debugger requests the open API of APISIX, while APISIX goes and requests the SERVERS in the original values.

我们可以这样看待这个问题,在API调试过程中,它是调试器实际请求的地址,因此它应当是APISIX对外提供的地址,而原始值应该是上游的。
因此实际上,调试器请求APISIX开放的API,而APISIX去请求原始值中的servers。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bzp2010 I suggest that this PR not deal with servers first, and deal with it later if there is a strong demand; instead of dealing with imperfections now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the choices to make here is whether to use servers as route.host or upstream.node, which is really critical.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nic-chen Well, I decided to remove this feature and replace it with a default empty upstream that the user can simply modify to change the upstream associated with all routes.

@bzp2010 bzp2010 requested a review from starsz June 7, 2022 02:09
@bzp2010
Copy link
Contributor Author

bzp2010 commented Jun 10, 2022

Update

  • Route IDs are no longer used (now generated by Dashboard itself)
  • The route name generation algorithm has been updated and it now uses the following rule

TaskName_consumer/{cid}[_GET/POST]

taskName: test

methodMerge: true
Method + URI:
GET|POST /consumer/{cid} => test_consumer/{cid}

methodMerge: false
Method + URI:
GET|POST /consumer/{cid} => test_consumer/{cid}_GET  +  test_consumer/{cid}_POST

Request for Comments

In response to this, I would like to seek more suggestions and I believe there are the following needs met:

  • Total length will not be long
  • Try not to have symbols other than _, such as /{}, etc.
  • Can contain URIs and methods
  • No dependency on specific fields of OpenAPI (as none of them may exist)

cc @nic-chen @starsz @tokers @tao12345666333

@bzp2010 bzp2010 requested a review from nic-chen June 10, 2022 09:17
@nic-chen
Copy link
Member

will review it later.

// Route
assert.Equal(t, data.Upstreams[0].ID, data.Routes[0].UpstreamID)
for _, route := range data.Routes {
fmt.Println(route.Name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, removed

// temporarily save the parsed data
data = &loader.DataSets{}
// global upstream ID
globalUpstreamID = o.TaskName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the taskName as upstream ID ?
It looks strange.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The taskname can be set to a service name, such as user or order, so that it will generate an upstream named order and generate a route name similar to order_user/list. I think this is clearer.


d, ok := input.([]byte)
if !ok {
return nil, fmt.Errorf("input format error: expected []byte but it is %s", reflect.TypeOf(input).Kind().String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, it's a sort of programming faults so I would suggest using panic here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tokers 🤔

For the input data error, should we classify it as a 400 Bad Request instead of a 5xx server-related error? I don't think this should be a programming error on the server.
From a practical point of view, if we use panic here, the Manager API will simply treat it as a 500 Internal Server Error and not automatically select the appropriate response code or have us specify it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the input data error, should we classify it as a 400 Bad Request instead of a 5xx server-related error?

Is this an API and will be exposed to the client? If so, you need to use some standards to make sure the data type is correct, such as jsonschema. If not, it means this method will be called by another internal method, and it's that handler's responsibility to make sure it accepts correct data type from the client and pass them to this method. As a result, when this method accepts bad data type, it could be classified to the programming fault.

This comment was marked as outdated.

Copy link
Contributor Author

@bzp2010 bzp2010 Jun 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an API and will be exposed to the client? If so, you need to use some standards to make sure the data type is correct, such as jsonschema. If not, it means this method will be called by another internal method, and it's that handler's responsibility to make sure it accepts correct data type from the client and pass them to this method. As a result, when this method accepts bad data type, it could be classified to the programming fault.

Get it, I will change it to panic and move the conversion check logic to the handler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the input error to panic


// no paths in OAS3 document
if len(swagger.Paths) <= 0 {
return nil, consts.ErrImportFile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is not enough.

Use errors.Wrap to carry the context message. See https://github.com/pkg/errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is not enough, I will add more error messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error context improved

},
}

u, err := url.Parse(servers[0].URL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be the URL exposed by the gateway I guess.

assert.Contains(t, route.Methods, "DELETE")
assert.Equal(t, "Remove customer", route.Desc)
assert.Equal(t, entity.Status(0), route.Status)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add the default arm and run t.Fail since a route with bad name exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default branch added

put:
tags:
- default
summary: Update customer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we support parse summary ?

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@starsz After confirmation, they are written to the desc field of the route, which is now supported.

// replace parameter in uri to wildcard
realUri := regURIVar.ReplaceAllString(uri, "*")
// generate route Name
routeName := o.TaskName + "_" + strings.TrimPrefix(uri, "/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can follow APISIX's schema to check resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tao12345666333 Yes, the ability to check the schema is already built into the storage layer, so no matter what APISIX resource we try to create, it will perform the schema check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I'm still not sure on the route name generation and can see from #2460 (comment), do you have any suggestions?

@juzhiyuan juzhiyuan merged commit 360ef07 into apache:master Jun 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants