-
Notifications
You must be signed in to change notification settings - Fork 264
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
feat: Add service import command #1065
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dsimansk: 4 warnings.
In response to this:
Description
The PR is missing any kind of test coverage, TBD. I'd like to gather early feedback on the implementation approach here.
Changes
- Add
service import
commandReference
Fixes #654
/lint
/hold
/cc @rhuss @navidshaikh
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.
pkg/serving/v1/client_mock.go
Outdated
@@ -192,6 +192,26 @@ func (c *MockKnServingClient) GetConfiguration(name string) (*servingv1.Configur | |||
return call.Result[0].(*servingv1.Configuration), mock.ErrorOrNil(call.Result[1]) | |||
} | |||
|
|||
// Create a new revision |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golint comments: comment on exported method ServingRecorder.CreateRevision should be of the form "CreateRevision ...". More info.
pkg/serving/v1/client_mock.go
Outdated
return mock.ErrorOrNil(call.Result[0]) | ||
} | ||
|
||
// Update the given revision |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golint comments: comment on exported method ServingRecorder.UpdateRevision should be of the form "UpdateRevision ...". More info.
sr.r.Add("UpdateRevision", []interface{}{revision}, []interface{}{err}) | ||
} | ||
|
||
func (c *MockKnServingClient) UpdateRevision(revision *servingv1.Revision) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golint comments: exported method MockKnServingClient.UpdateRevision should have comment or be unexported. More info.
looks good. Have two questions /comments
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/ok-to-test
Nice start. I wonder if `—dry-runs option would be nice whereby the import verifies but does not deploy. Maybe as a follow on. Anyhow, love seeing this whole export/import working out. An obvious e2e test will be something like:
kn service create testSvc —image ...
...
kn service export testSvc test.yml
...
kn service delete testSvc
...
kn service import testSvc test.yml
And end result is the same as first step :)
Per our conversation with @rhuss
That's intentional to avoid error states. During my local testing I've seen intermittent failures during wait phase. Those might have been caused by waiting issue that was recently fixed. Thanks for the suggestion, I'll double check if there's still an issue or not.
Thanks for the suggestion, |
pkg/kn/commands/service/import.go
Outdated
tmp.OwnerReferences = []metav1.OwnerReference{ | ||
{ | ||
APIVersion: currentConf.APIVersion, | ||
Kind: currentConf.Kind, | ||
Name: currentConf.Name, | ||
UID: currentConf.UID, | ||
Controller: ptr.Bool(true), | ||
BlockOwnerDeletion: ptr.Bool(true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls check whether you can use the kmeta
package to set the owner reference. Below is from the serving repo
https://github.com/knative/serving/blob/5233f0c607c5a89d6a58d6faac9eed57b4877b79/pkg/reconciler/revision/resources/deploy.go#L253
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice find, thanks! It's exactly the function needed here.
@dsimansk please let me know when this PR is ready from your side (so no WIP anymore), I would then jump on the review, too. |
289b707
to
b013632
Compare
@rhuss ready now. 😺 /unhold |
@rhuss @navidshaikh @itsmurugappan There's one scenario I'm not really sure how to handle.
Cmd output
Cmd output
It feels like inconsistent behaviour of |
It seems that controller was still handling the subsequent creation of |
IMO this is a bug on kn service export, whenever the mode is export , kn should give a yaml in the Export kind instead of KSVC kind. @rhuss @navidshaikh please pitch in. |
I've opened a new issue to track export bug. #1087 |
4b8b24f
to
79bbf1e
Compare
pkg/kn/commands/service/import.go
Outdated
// Retrieve current Configuration to be use in OwnerReference | ||
retries := 0 | ||
var currentConf *servingv1.Configuration | ||
for { | ||
currentConf, err = client.GetConfiguration(serviceName) | ||
if err != nil { | ||
if apierrors.IsNotFound(err) && retries < 5 { | ||
retries++ | ||
time.Sleep(time.Second) | ||
continue | ||
} else { | ||
return err | ||
} | ||
} | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a simple retry-loop to retrieve Configuration to verify it it's causing e2e test to fail. The retry can be moved to client like UpdateServiceWithRetry
. Let me know if there are any objections to actual retry or it could be done differently.
The code should resolve the following error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also retry.OnError
from client-go utils that's usable here, if there's a need for more sophisticated approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am with fine this approach;
beside I think we can separate this retry logic in a dedicated function, but then if we'd to separate it out anyway we can consume the clint-go utils you linked. Infact we should consider using these utils wherever we find a fit to encourage consumption from these standard libs we are vendoring. (all this can be done in a subsequent iteration if thats feasible).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of using standard libs. I've pushed an updated version in 7b8317c. We can start small here and incrementally update UpdateServiceWithRetry
or any other instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggested a few nits and a wait for service related question, also please add CHANGELOG entry
docs/cmd/kn_service_import.md
Outdated
### Options | ||
|
||
``` | ||
--async DEPRECATED: please use --no-wait instead. Do not wait for 'service import' operation to be completed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can stop --async
support now, it has been 3+ releases since we introduced --no-wait?
pkg/kn/commands/service/import.go
Outdated
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
"knative.dev/pkg/kmeta" | ||
servingv1 "knative.dev/serving/pkg/apis/serving/v1" | ||
|
||
"github.com/spf13/cobra" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/util/yaml" | ||
"knative.dev/client/pkg/kn/commands" | ||
clientservingv1 "knative.dev/client/pkg/serving/v1" | ||
|
||
clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regroup wrt third party and knative.dev/client imports?
pkg/kn/commands/service/import.go
Outdated
Use: "import FILENAME", | ||
Short: "Import a service and its revisions", | ||
Example: ` | ||
# Import a service in YAML format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Import a service in YAML format | |
# Import a service from YAML format |
pkg/kn/commands/service/import.go
Outdated
# Import a service in YAML format | ||
kn service import /path/to/file.yaml | ||
|
||
# Import a service in JSON format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Import a service in JSON format | |
# Import a service from JSON format |
pkg/kn/commands/service/import.go
Outdated
return err | ||
} | ||
if export.Spec.Service.Name == "" { | ||
return fmt.Errorf("provided import doesn't content service name, please note that only kn' custom Export format is supported") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("provided import doesn't content service name, please note that only kn' custom Export format is supported") | |
return fmt.Errorf("provided import file doesn't contain service name, please note that only kn's custom Export format is supported") |
pkg/kn/commands/service/import.go
Outdated
// Retrieve current Configuration to be use in OwnerReference | ||
retries := 0 | ||
var currentConf *servingv1.Configuration | ||
for { | ||
currentConf, err = client.GetConfiguration(serviceName) | ||
if err != nil { | ||
if apierrors.IsNotFound(err) && retries < 5 { | ||
retries++ | ||
time.Sleep(time.Second) | ||
continue | ||
} else { | ||
return err | ||
} | ||
} | ||
break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am with fine this approach;
beside I think we can separate this retry logic in a dedicated function, but then if we'd to separate it out anyway we can consume the clint-go utils you linked. Infact we should consider using these utils wherever we find a fit to encourage consumption from these standard libs we are vendoring. (all this can be done in a subsequent iteration if thats feasible).
pkg/kn/commands/service/import.go
Outdated
// Create revision with current Configuration's OwnerReference | ||
if len(export.Spec.Revisions) > 0 { | ||
for _, r := range export.Spec.Revisions { | ||
tmp := r |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do deep copy here?
} | ||
} | ||
|
||
err = waitIfRequested(client, serviceName, waitFlags, "Importing", "imported", out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a service has 5 revisions among which 2 are active (configured in traffic block); will service still report true if any of those 3 non-active revisions failed to become ready ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current service export
impl doesn't produce such export output, only active revisions are included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether we should allow later in service export
, too, to export all revisions, so that the import then also would import the history. But let's leave that question for future refinement of this feature.
pkg/kn/commands/service/import.go
Outdated
if err != nil { | ||
return err | ||
} | ||
|
||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err != nil { | |
return err | |
} | |
return nil | |
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks ! Looks good in general, but I'm not yet 100% sure how it behaves in the wild. E.g. whether the ordering is correct (which is also reflected in kn revision list
). I'm also not sure if the export includes the latest revision generated from the service in the revision list, because in this case we do have an issue, as the initial service creation implicitly also creates a revision.
We definitely require an E2E test for the long run, to verify the full behaviour. But for now I would be happy to merge this, but also would mark the command as "experimental" (would add this to the changelog.adoc and the command description)
serviceName, client.Namespace()) | ||
} | ||
|
||
err = client.CreateService(&export.Spec.Service) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when we create a service like this, then an initial revision is created. This is fine, but I think we might get into ordering issues as the generation of this revision would be 1 (and which might conflict with the revision history that we are importing, where there would be also 1 generation). At least this is what I suspect. (would need to be verified)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, your suspicion is exactly on point here, revision history is mangled after import. I'll try to double-check if there's an alternative to the current import order.
➜ client git:(service-import) kn revision list
NAME SERVICE TRAFFIC TAGS GENERATION AGE CONDITIONS READY REASON
hello-xbgkn-2 hello 25% 2 60s 4 OK / 4 True
hello-brqcf-3 hello 50% 1 60s 4 OK / 4 True
hello-kphzr-1 hello 25% 1 60s 4 OK / 4 True
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently revision list is sorted by
// revisionListSortFunc sorts by namespace, service, generation and name
In fact it's Configuration's generation, here. That's in line with revision history in my comment above. However, I don't see any clean way how to reproduce the history in the import wihout leaning towards "replay" approach actually.
} | ||
} | ||
|
||
err = waitIfRequested(client, serviceName, waitFlags, "Importing", "imported", out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether we should allow later in service export
, too, to export all revisions, so that the import then also would import the history. But let's leave that question for future refinement of this feature.
r.GetService("foo", nil, nil) | ||
_, err = executeServiceCommand(client, "import", file) | ||
|
||
assert.ErrorContains(t, err, "because the service already exists") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, for tests, I would assert that all required context information is included, i.e. here the service name and maybe the namespace (if this is part of the error message). The exact wording is secondary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true for the other checks below, too, that check for some messages
@@ -202,6 +202,28 @@ func (c *MockKnServingClient) GetConfiguration(name string) (*servingv1.Configur | |||
return call.Result[0].(*servingv1.Configuration), mock.ErrorOrNil(call.Result[1]) | |||
} | |||
|
|||
// CreateRevision records a call CreateRevision |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please also add a call to the recorder and the mock methods to client_mock_test
? This keeps the test coverage happy :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I've missed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beside the issue of building up revision with the proper generation ordering, this PR looks good to me.
I suggest, to merge this PR as now (it's still marked as experimental), but think about another solution after this.
E.g would it be possible to remove the revision that has been created implicitly when we created the Service and after we have added the revision list ? Also we could experiment with building up a Configuration first and only add the Service
as a final step (with the same name as the Configuration, as this is the correlation between Service and Configuration). Also, the ownerReference needs between Service and Configuration then would be needed to be set as the last step.
wdyt, should we move forward now ?
Co-authored-by: Roland Huß <[email protected]>
Co-authored-by: Roland Huß <[email protected]>
b175789
to
e29755d
Compare
The following is the coverage report on the affected files.
|
@rhuss sure, sounds good to me. I've been experimenting with temp initial revision to recreate generation history correctly without too much luck yet. I can explore the options further, but it shouldn't prevent "experimental" import to be merged now. |
Ok, then let's go for it /Lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: dsimansk, maximilien, rhuss The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Description
The PR is missing any kind of test coverage, TBD. I'd like to gather early feedback on the implementation approach here.
Changes
service import
commandReference
Fixes #654
/lint
/hold
/cc @rhuss @navidshaikh