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

feat: Add service import command #1065

Merged
merged 11 commits into from
Nov 9, 2020

Conversation

dsimansk
Copy link
Contributor

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 command

Reference

Fixes #654

/lint
/hold
/cc @rhuss @navidshaikh

@knative-prow-robot knative-prow-robot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Oct 15, 2020
Copy link
Contributor

@knative-prow-robot knative-prow-robot left a 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 command

Reference

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.

@@ -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
Copy link
Contributor

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 Show resolved Hide resolved
return mock.ErrorOrNil(call.Result[0])
}

// Update the given revision
Copy link
Contributor

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 {
Copy link
Contributor

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.

@google-cla google-cla bot added the cla: yes Indicates the PR's author has signed the CLA. label Oct 15, 2020
@knative-prow-robot knative-prow-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Oct 15, 2020
@itsmurugappan
Copy link
Contributor

looks good. Have two questions /comments

  1. Will this is only support only Export kind ? If I export in kubectl friendly style, will I not be able to use the import functionality
  2. I see initial service without traffic and traffic getting updated later. IIRC traffic with missing revisions should not cause the ksvc to fail. Could save you an additional step of updating the traffic.

Copy link
Contributor

@maximilien maximilien left a 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 :)

@knative-prow-robot knative-prow-robot added the ok-to-test Indicates a non-member PR verified by an org member that is safe to test. label Oct 15, 2020
@dsimansk
Copy link
Contributor Author

1. Will this is only support only Export kind ? If I export in kubectl friendly style, will I not be able to use the import functionality

Per our conversation with @rhuss kn shouldn't duplicate functionality as such export is already usable with kubectl. Therefore kn service import should work with its own Export format. Please @rhuss correct me if I'm wrong here.

2. I see initial service without traffic and traffic getting updated later. IIRC traffic with missing revisions should not cause the ksvc to fail. Could save you an additional step of updating the traffic.

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.

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:

Thanks for the suggestion, --dry-run sounds useful especially in this context.

Comment on lines 115 to 122
tmp.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: currentConf.APIVersion,
Kind: currentConf.Kind,
Name: currentConf.Name,
UID: currentConf.UID,
Controller: ptr.Bool(true),
BlockOwnerDeletion: ptr.Bool(true),
Copy link
Contributor

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

Copy link
Contributor Author

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.

@rhuss
Copy link
Contributor

rhuss commented Oct 27, 2020

@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.

@knative-prow-robot knative-prow-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Oct 30, 2020
@dsimansk dsimansk changed the title WIP: feat: Add service import command feat: Add service import command Oct 30, 2020
@knative-prow-robot knative-prow-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 30, 2020
@dsimansk
Copy link
Contributor Author

@rhuss ready now. 😺

/unhold

@knative-prow-robot knative-prow-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Oct 30, 2020
@dsimansk
Copy link
Contributor Author

@rhuss @navidshaikh @itsmurugappan

There's one scenario I'm not really sure how to handle.

  • Let's have simple hello ksvc without additional revisions
  • Exec: kn service export hello -o yaml --mode export
Cmd output
➜  client git:(service-import) kn service export hello -o yaml --mode export                                  
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  creationTimestamp: null
  name: hello
spec:
  template:
    metadata:
      annotations:
        client.knative.dev/user-image: docker.io/dsimansk/helloworld
      creationTimestamp: null
      name: hello-djrrc-1
    spec:
      containerConcurrency: 0
      containers:
      - env:
        - name: TARGET
          value: v1
        image: docker.io/dsimansk/helloworld
        name: user-container
        readinessProbe:
          successThreshold: 1
          tcpSocket:
            port: 0
        resources: {}
      enableServiceLinks: false
      timeoutSeconds: 300
status: {}
  • For this case --mode flag is ignored and plain ksvc output is produces

  • As a result there's no usable Export type object for the import to use.

    • If import would support plain ksvc yaml than there's a duplicite functionality with service create --filename, service apply and kubectl.
  • Exec: kn service export hello -o yaml --mode export --with-revisions

    • Adding --with-revisions will produce requested Export type
Cmd output
➜  client git:(service-import) kn service export hello -o yaml --mode export --with-revisions
apiVersion: client.knative.dev/v1alpha1
kind: Export
metadata:
  creationTimestamp: null
spec:
  revisions: null
  service:
    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      creationTimestamp: null
      name: hello
    spec:
      template:
        metadata:
          annotations:
            client.knative.dev/user-image: docker.io/dsimansk/helloworld
          creationTimestamp: null
          name: hello-djrrc-1
        spec:
          containerConcurrency: 0
          containers:
          - env:
            - name: TARGET
              value: v1
            image: docker.io/dsimansk/helloworld
            name: user-container
            readinessProbe:
              successThreshold: 1
              tcpSocket:
                port: 0
            resources: {}
          enableServiceLinks: false
          timeoutSeconds: 300
    status: {}

It feels like inconsistent behaviour of --mode flag, that's actually depending on --with-revisions to produce expected result.

@dsimansk
Copy link
Contributor Author

        🦆 kn service import /tmp/kn-file993807706/foo-with-revisions --namespace kne2etests16
        ┃ 
        🔥 Error: configurations.serving.knative.dev "foo" not found
        🔥 Run 'kn --help' for usage
        🔥 

https://github.com/knative/client/pull/1065/files#diff-89bebfee01d13203ce4186956ccc919b30072d4ca985d3c5b054336ce0c735f0R105-R109

It seems that controller was still handling the subsequent creation of Configuration for the Service.
Would you mind something like GetConfigurationWithRetry() or watch?

@itsmurugappan
Copy link
Contributor

It feels like inconsistent behaviour of --mode flag, that's actually depending on --with-revisions to produce expected result.

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.

@dsimansk
Copy link
Contributor Author

dsimansk commented Nov 2, 2020

I've opened a new issue to track export bug. #1087

Comment on lines 108 to 112
// 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
}
Copy link
Contributor Author

@dsimansk dsimansk Nov 3, 2020

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

@navidshaikh navidshaikh Nov 4, 2020

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).

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 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.

Copy link
Collaborator

@navidshaikh navidshaikh left a 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

### Options

```
--async DEPRECATED: please use --no-wait instead. Do not wait for 'service import' operation to be completed.
Copy link
Collaborator

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?

Comment on lines 24 to 30
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"
Copy link
Collaborator

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?

Use: "import FILENAME",
Short: "Import a service and its revisions",
Example: `
# Import a service in YAML format
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Import a service in YAML format
# Import a service from YAML format

# Import a service in YAML format
kn service import /path/to/file.yaml

# Import a service in JSON format
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Import a service in JSON format
# Import a service from JSON format

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")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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")

Comment on lines 108 to 112
// 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
}
Copy link
Collaborator

@navidshaikh navidshaikh Nov 4, 2020

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).

// Create revision with current Configuration's OwnerReference
if len(export.Spec.Revisions) > 0 {
for _, r := range export.Spec.Revisions {
tmp := r
Copy link
Collaborator

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)
Copy link
Collaborator

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 ?

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 current service export impl doesn't produce such export output, only active revisions are included.

Copy link
Contributor

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.

Comment on lines 138 to 142
if err != nil {
return err
}

return nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if err != nil {
return err
}
return nil
return err

@navidshaikh navidshaikh added this to the v0.19.0 milestone Nov 5, 2020
Copy link
Contributor

@rhuss rhuss left a 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)

pkg/kn/commands/service/import.go Outdated Show resolved Hide resolved
serviceName, client.Namespace())
}

err = client.CreateService(&export.Spec.Service)
Copy link
Contributor

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)

Copy link
Contributor Author

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   

Copy link
Contributor Author

@dsimansk dsimansk Nov 5, 2020

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)
Copy link
Contributor

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 Show resolved Hide resolved
r.GetService("foo", nil, nil)
_, err = executeServiceCommand(client, "import", file)

assert.ErrorContains(t, err, "because the service already exists")
Copy link
Contributor

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.

Copy link
Contributor

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

pkg/serving/v1/client_mock.go Outdated Show resolved Hide resolved
@@ -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
Copy link
Contributor

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 :)

Copy link
Contributor Author

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.

Copy link
Contributor

@maximilien maximilien left a comment

Choose a reason for hiding this comment

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

LGTM

@knative-prow-robot knative-prow-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 5, 2020
Copy link
Contributor

@rhuss rhuss left a 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 ?

CHANGELOG.adoc Outdated Show resolved Hide resolved
@knative-metrics-robot
Copy link

The following is the coverage report on the affected files.
Say /test pull-knative-client-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/kn/commands/service/import.go Do not exist 80.0%
pkg/kn/commands/service/service.go 91.3% 91.7% 0.4
pkg/serving/v1/client.go 68.0% 65.4% -2.7
pkg/serving/v1/client_mock.go 93.7% 94.2% 0.6

@dsimansk
Copy link
Contributor Author

dsimansk commented Nov 9, 2020

@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.

@rhuss
Copy link
Contributor

rhuss commented Nov 9, 2020

Ok, then let's go for it

/Lgtm
/Approve

@knative-prow-robot knative-prow-robot added the lgtm Indicates that a PR is ready to be merged. label Nov 9, 2020
@knative-prow-robot
Copy link
Contributor

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@knative-prow-robot knative-prow-robot merged commit b72e4be into knative:master Nov 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cla: yes Indicates the PR's author has signed the CLA. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "kn service import"
7 participants