From 20c91d6fce402371785e3d78603ae41c48c191a3 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Fri, 15 Feb 2019 14:08:43 +0100 Subject: [PATCH 1/3] Bump csi-test and csi-lib-util --- Gopkg.lock | 19 +- Gopkg.toml | 5 +- .../csi-lib-utils/connection/connection.go | 310 ++++++++++++++++++ .../protosanitizer/protosanitizer.go | 55 +++- .../csi-lib-utils/release-tools/LICENSE | 201 ++++++++++++ .../kubernetes-csi/csi-test/driver/mock.go | 12 +- 6 files changed, 580 insertions(+), 22 deletions(-) create mode 100644 vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go create mode 100644 vendor/github.com/kubernetes-csi/csi-lib-utils/release-tools/LICENSE diff --git a/Gopkg.lock b/Gopkg.lock index 15f64025..27e494d4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -136,23 +136,26 @@ version = "v1.1.5" [[projects]] - digest = "1:610f50d23ee3e0fa37d77fc23bf2c3b6897d2dc1c934739f13cdf551e9fc57d9" + digest = "1:b09c9ac14d93f481c768e73a12d4e8527ac25232d593f2ad918ffe462901b654" name = "github.com/kubernetes-csi/csi-lib-utils" - packages = ["protosanitizer"] + packages = [ + "connection", + "protosanitizer", + ] pruneopts = "NUT" - revision = "1628ab5351eafa4fc89a96862a08a891e601e03a" - version = "v0.1.0" + revision = "e581edfed23c6ce10243bc0efb2292cf68b83e1d" + version = "v0.3.1" [[projects]] - digest = "1:cab5d1fe86e273b35887f707dbec779d77d87613d9f2f14ea23002912197ce81" + digest = "1:0f47ba38b647bb8e7cddb71c3341134b2c2eaa8cef2af82291b5c9870ee7f572" name = "github.com/kubernetes-csi/csi-test" packages = [ "driver", "utils", ] pruneopts = "NUT" - revision = "42947e04c4a0d2087448841a1dc3ccb20fb903b1" - version = "v1.0.0-rc2" + revision = "722eead38c269060656e0fc91f280610ea56f19b" + version = "v1.0.3" [[projects]] digest = "1:bdca59c6bbfc61fea8b66d564f54a0be4364728b1ee94a004eee21ae2c01582d" @@ -683,11 +686,11 @@ "github.com/davecgh/go-spew/spew", "github.com/golang/mock/gomock", "github.com/golang/protobuf/proto", + "github.com/kubernetes-csi/csi-lib-utils/connection", "github.com/kubernetes-csi/csi-lib-utils/protosanitizer", "github.com/kubernetes-csi/csi-test/driver", "google.golang.org/grpc", "google.golang.org/grpc/codes", - "google.golang.org/grpc/connectivity", "google.golang.org/grpc/status", "k8s.io/api/core/v1", "k8s.io/api/storage/v1", diff --git a/Gopkg.toml b/Gopkg.toml index fc352acf..29b409cd 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -8,10 +8,9 @@ name = "github.com/golang/protobuf" version = "1.1.0" - [[constraint]] name = "github.com/kubernetes-csi/csi-test" - version = "v1.0.0-1" + version = "~v1.0.3" [[constraint]] name = "google.golang.org/grpc" @@ -44,7 +43,7 @@ [[constraint]] name = "github.com/kubernetes-csi/csi-lib-utils" - version = "0.1.0" + version = "0.3.1" [prune] non-go = true diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go new file mode 100644 index 00000000..588826ee --- /dev/null +++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go @@ -0,0 +1,310 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package connection + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "net" + "strings" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" + "google.golang.org/grpc" + "k8s.io/klog" +) + +const ( + // Interval of logging connection errors + connectionLoggingInterval = 10 * time.Second + + // Interval of trying to call Probe() until it succeeds + probeInterval = 1 * time.Second +) + +const terminationLogPath = "/dev/termination-log" + +// Connect opens insecure gRPC connection to a CSI driver. Address must be either absolute path to UNIX domain socket +// file or have format '://', following gRPC name resolution mechanism at +// https://github.com/grpc/grpc/blob/master/doc/naming.md. +// +// The function tries to connect indefinitely every second until it connects. The function automatically disables TLS +// and adds interceptor for logging of all gRPC messages at level 5. +// +// For a connection to a Unix Domain socket, the behavior after +// loosing the connection is configurable. The default is to +// log the connection loss and reestablish a connection. Applications +// which need to know about a connection loss can be notified by +// passing a callback with OnConnectionLoss and in that callback +// can decide what to do: +// - exit the application with os.Exit +// - invalidate cached information +// - disable the reconnect, which will cause all gRPC method calls to fail with status.Unavailable +// +// For other connections, the default behavior from gRPC is used and +// loss of connection is not detected reliably. +func Connect(address string, options ...Option) (*grpc.ClientConn, error) { + return connect(address, []grpc.DialOption{}, options) +} + +// Option is the type of all optional parameters for Connect. +type Option func(o *options) + +// OnConnectionLoss registers a callback that will be invoked when the +// connection got lost. If that callback returns true, the connection +// is reestablished. Otherwise the connection is left as it is and +// all future gRPC calls using it will fail with status.Unavailable. +func OnConnectionLoss(reconnect func() bool) Option { + return func(o *options) { + o.reconnect = reconnect + } +} + +// ExitOnConnectionLoss returns callback for OnConnectionLoss() that writes +// an error to /dev/termination-log and exits. +func ExitOnConnectionLoss() func() bool { + return func() bool { + terminationMsg := "Lost connection to CSI driver, exiting" + if err := ioutil.WriteFile(terminationLogPath, []byte(terminationMsg), 0644); err != nil { + klog.Errorf("%s: %s", terminationLogPath, err) + } + klog.Fatalf(terminationMsg) + return false + } +} + +type options struct { + reconnect func() bool +} + +// connect is the internal implementation of Connect. It has more options to enable testing. +func connect(address string, dialOptions []grpc.DialOption, connectOptions []Option) (*grpc.ClientConn, error) { + var o options + for _, option := range connectOptions { + option(&o) + } + + dialOptions = append(dialOptions, + grpc.WithInsecure(), // Don't use TLS, it's usually local Unix domain socket in a container. + grpc.WithBackoffMaxDelay(time.Second), // Retry every second after failure. + grpc.WithBlock(), // Block until connection succeeds. + grpc.WithUnaryInterceptor(LogGRPC), // Log all messages. + ) + unixPrefix := "unix://" + if strings.HasPrefix(address, "/") { + // It looks like filesystem path. + address = unixPrefix + address + } + + if strings.HasPrefix(address, unixPrefix) { + // state variables for the custom dialer + haveConnected := false + lostConnection := false + reconnect := true + + dialOptions = append(dialOptions, grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + if haveConnected && !lostConnection { + // We have detected a loss of connection for the first time. Decide what to do... + // Record this once. TODO (?): log at regular time intervals. + klog.Errorf("Lost connection to %s.", address) + // Inform caller and let it decide? Default is to reconnect. + if o.reconnect != nil { + reconnect = o.reconnect() + } + lostConnection = true + } + if !reconnect { + return nil, errors.New("connection lost, reconnecting disabled") + } + conn, err := net.DialTimeout("unix", address[len(unixPrefix):], timeout) + if err == nil { + // Connection restablished. + haveConnected = true + lostConnection = false + } + return conn, err + })) + } else if o.reconnect != nil { + return nil, errors.New("OnConnectionLoss callback only supported for unix:// addresses") + } + + klog.Infof("Connecting to %s", address) + + // Connect in background. + var conn *grpc.ClientConn + var err error + ready := make(chan bool) + go func() { + conn, err = grpc.Dial(address, dialOptions...) + close(ready) + }() + + // Log error every connectionLoggingInterval + ticker := time.NewTicker(connectionLoggingInterval) + defer ticker.Stop() + + // Wait until Dial() succeeds. + for { + select { + case <-ticker.C: + klog.Warningf("Still connecting to %s", address) + + case <-ready: + return conn, err + } + } +} + +// LogGRPC is gPRC unary interceptor for logging of CSI messages at level 5. It removes any secrets from the message. +func LogGRPC(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + klog.V(5).Infof("GRPC call: %s", method) + klog.V(5).Infof("GRPC request: %s", protosanitizer.StripSecrets(req)) + err := invoker(ctx, method, req, reply, cc, opts...) + klog.V(5).Infof("GRPC response: %s", protosanitizer.StripSecrets(reply)) + klog.V(5).Infof("GRPC error: %v", err) + return err +} + +// GetDriverName returns name of CSI driver. +func GetDriverName(ctx context.Context, conn *grpc.ClientConn) (string, error) { + client := csi.NewIdentityClient(conn) + + req := csi.GetPluginInfoRequest{} + rsp, err := client.GetPluginInfo(ctx, &req) + if err != nil { + return "", err + } + name := rsp.GetName() + if name == "" { + return "", fmt.Errorf("driver name is empty") + } + return name, nil +} + +// PluginCapabilitySet is set of CSI plugin capabilities. Only supported capabilities are in the map. +type PluginCapabilitySet map[csi.PluginCapability_Service_Type]bool + +// GetPluginCapabilities returns set of supported capabilities of CSI driver. +func GetPluginCapabilities(ctx context.Context, conn *grpc.ClientConn) (PluginCapabilitySet, error) { + client := csi.NewIdentityClient(conn) + req := csi.GetPluginCapabilitiesRequest{} + rsp, err := client.GetPluginCapabilities(ctx, &req) + if err != nil { + return nil, err + } + caps := PluginCapabilitySet{} + for _, cap := range rsp.GetCapabilities() { + if cap == nil { + continue + } + srv := cap.GetService() + if srv == nil { + continue + } + t := srv.GetType() + caps[t] = true + } + return caps, nil +} + +// ControllerCapabilitySet is set of CSI controller capabilities. Only supported capabilities are in the map. +type ControllerCapabilitySet map[csi.ControllerServiceCapability_RPC_Type]bool + +// GetControllerCapabilities returns set of supported controller capabilities of CSI driver. +func GetControllerCapabilities(ctx context.Context, conn *grpc.ClientConn) (ControllerCapabilitySet, error) { + client := csi.NewControllerClient(conn) + req := csi.ControllerGetCapabilitiesRequest{} + rsp, err := client.ControllerGetCapabilities(ctx, &req) + if err != nil { + return nil, err + } + + caps := ControllerCapabilitySet{} + for _, cap := range rsp.GetCapabilities() { + if cap == nil { + continue + } + rpc := cap.GetRpc() + if rpc == nil { + continue + } + t := rpc.GetType() + caps[t] = true + } + return caps, nil +} + +// ProbeForever calls Probe() of a CSI driver and waits until the driver becomes ready. +// Any error other than timeout is returned. +func ProbeForever(conn *grpc.ClientConn, singleProbeTimeout time.Duration) error { + for { + klog.Info("Probing CSI driver for readiness") + ready, err := probeOnce(conn, singleProbeTimeout) + if err != nil { + st, ok := status.FromError(err) + if !ok { + // This is not gRPC error. The probe must have failed before gRPC + // method was called, otherwise we would get gRPC error. + return fmt.Errorf("CSI driver probe failed: %s", err) + } + if st.Code() != codes.DeadlineExceeded { + return fmt.Errorf("CSI driver probe failed: %s", err) + } + // Timeout -> driver is not ready. Fall through to sleep() below. + klog.Warning("CSI driver probe timed out") + } else { + if ready { + return nil + } + klog.Warning("CSI driver is not ready") + } + // Timeout was returned or driver is not ready. + time.Sleep(probeInterval) + } +} + +// probeOnce is a helper to simplify defer cancel() +func probeOnce(conn *grpc.ClientConn, timeout time.Duration) (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return Probe(ctx, conn) +} + +// Probe calls driver Probe() just once and returns its result without any processing. +func Probe(ctx context.Context, conn *grpc.ClientConn) (ready bool, err error) { + client := csi.NewIdentityClient(conn) + + req := csi.ProbeRequest{} + rsp, err := client.Probe(ctx, &req) + + if err != nil { + return false, err + } + + r := rsp.GetReady() + if r == nil { + // "If not present, the caller SHALL assume that the plugin is in a ready state" + return true, nil + } + return r.GetValue(), nil +} diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/protosanitizer/protosanitizer.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/protosanitizer/protosanitizer.go index d75e0e21..af64a7b2 100644 --- a/vendor/github.com/kubernetes-csi/csi-lib-utils/protosanitizer/protosanitizer.go +++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/protosanitizer/protosanitizer.go @@ -24,10 +24,10 @@ import ( "reflect" "strings" - "github.com/container-storage-interface/spec/lib/go/csi" "github.com/golang/protobuf/descriptor" "github.com/golang/protobuf/proto" protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" + protobufdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" ) // StripSecrets returns a wrapper around the original CSI gRPC message @@ -36,15 +36,27 @@ import ( // Instead of the secret value(s), the string "***stripped***" is // included in the result. // +// StripSecrets relies on an extension in CSI 1.0 and thus can only +// be used for messages based on that or a more recent spec! +// // StripSecrets itself is fast and therefore it is cheap to pass the // result to logging functions which may or may not end up serializing // the parameter depending on the current log level. func StripSecrets(msg interface{}) fmt.Stringer { - return &stripSecrets{msg} + return &stripSecrets{msg, isCSI1Secret} +} + +// StripSecretsCSI03 is like StripSecrets, except that it works +// for messages based on CSI 0.3 and older. It does not work +// for CSI 1.0, use StripSecrets for that. +func StripSecretsCSI03(msg interface{}) fmt.Stringer { + return &stripSecrets{msg, isCSI03Secret} } type stripSecrets struct { msg interface{} + + isSecretField func(field *protobuf.FieldDescriptorProto) bool } func (s *stripSecrets) String() string { @@ -60,7 +72,7 @@ func (s *stripSecrets) String() string { } // Now remove secrets from the generic representation of the message. - strip(parsed, s.msg) + s.strip(parsed, s.msg) // Re-encoded the stripped representation and return that. b, err = json.Marshal(parsed) @@ -70,7 +82,7 @@ func (s *stripSecrets) String() string { return string(b) } -func strip(parsed interface{}, msg interface{}) { +func (s *stripSecrets) strip(parsed interface{}, msg interface{}) { protobufMsg, ok := msg.(descriptor.Message) if !ok { // Not a protobuf message, so we are done. @@ -93,8 +105,7 @@ func strip(parsed interface{}, msg interface{}) { fields := md.GetField() if fields != nil { for _, field := range fields { - ex, err := proto.GetExtension(field.Options, csi.E_CsiSecret) - if err == nil && ex != nil && *ex.(*bool) { + if s.isSecretField(field) { // Overwrite only if already set. if _, ok := parsedFields[field.GetName()]; ok { parsedFields[field.GetName()] = "***stripped***" @@ -126,13 +137,41 @@ func strip(parsed interface{}, msg interface{}) { if slice, ok := entry.([]interface{}); ok { // Array of values, like VolumeCapabilities in CreateVolumeRequest. for _, entry := range slice { - strip(entry, i) + s.strip(entry, i) } } else { // Single value. - strip(entry, i) + s.strip(entry, i) } } } } } + +// isCSI1Secret uses the csi.E_CsiSecret extension from CSI 1.0 to +// determine whether a field contains secrets. +func isCSI1Secret(field *protobuf.FieldDescriptorProto) bool { + ex, err := proto.GetExtension(field.Options, e_CsiSecret) + return err == nil && ex != nil && *ex.(*bool) +} + +// Copied from the CSI 1.0 spec (https://github.com/container-storage-interface/spec/blob/37e74064635d27c8e33537c863b37ccb1182d4f8/lib/go/csi/csi.pb.go#L4520-L4527) +// to avoid a package dependency that would prevent usage of this package +// in repos using an older version of the spec. +// +// Future revision of the CSI spec must not change this extensions, otherwise +// they will break filtering in binaries based on the 1.0 version of the spec. +var e_CsiSecret = &proto.ExtensionDesc{ + ExtendedType: (*protobufdescriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 1059, + Name: "csi.v1.csi_secret", + Tag: "varint,1059,opt,name=csi_secret,json=csiSecret", + Filename: "github.com/container-storage-interface/spec/csi.proto", +} + +// isCSI03Secret relies on the naming convention in CSI <= 0.3 +// to determine whether a field contains secrets. +func isCSI03Secret(field *protobuf.FieldDescriptorProto) bool { + return strings.HasSuffix(field.GetName(), "_secrets") +} diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/release-tools/LICENSE b/vendor/github.com/kubernetes-csi/csi-lib-utils/release-tools/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/release-tools/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/kubernetes-csi/csi-test/driver/mock.go b/vendor/github.com/kubernetes-csi/csi-test/driver/mock.go index 9b051eee..0eed51d1 100644 --- a/vendor/github.com/kubernetes-csi/csi-test/driver/mock.go +++ b/vendor/github.com/kubernetes-csi/csi-test/driver/mock.go @@ -46,9 +46,9 @@ func NewMockCSIDriver(servers *MockCSIDriverServers) *MockCSIDriver { } } -func (m *MockCSIDriver) Start() error { - // Listen on a port assigned by the net package - l, err := net.Listen("tcp", "127.0.0.1:0") +// StartOnAddress starts a new gRPC server listening on given address. +func (m *MockCSIDriver) StartOnAddress(network, address string) error { + l, err := net.Listen(network, address) if err != nil { return err } @@ -61,6 +61,12 @@ func (m *MockCSIDriver) Start() error { return nil } +// Start starts a new gRPC server listening on a random TCP loopback port. +func (m *MockCSIDriver) Start() error { + // Listen on a port assigned by the net package + return m.StartOnAddress("tcp", "127.0.0.1:0") +} + func (m *MockCSIDriver) Nexus() (*grpc.ClientConn, error) { // Start server err := m.Start() From 795b662f5ce1d7726e3834595b9bdd973abe85b4 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Fri, 15 Feb 2019 14:42:16 +0100 Subject: [PATCH 2/3] Use common functions from util package --- cmd/csi-attacher/main.go | 34 +++------- pkg/connection/connection.go | 120 +++++------------------------------ 2 files changed, 24 insertions(+), 130 deletions(-) diff --git a/cmd/csi-attacher/main.go b/cmd/csi-attacher/main.go index cd7d9f06..e414715a 100644 --- a/cmd/csi-attacher/main.go +++ b/cmd/csi-attacher/main.go @@ -50,7 +50,7 @@ const ( var ( kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Required only when running out of cluster.") resync = flag.Duration("resync", 10*time.Minute, "Resync interval of the controller.") - connectionTimeout = flag.Duration("connection-timeout", 1*time.Minute, "Timeout for waiting for CSI driver socket.") + connectionTimeout = flag.Duration("connection-timeout", 0, "This option is deprecated.") csiAddress = flag.String("csi-address", "/run/csi/socket", "Address of the CSI driver socket.") dummy = flag.Bool("dummy", false, "Run in dummy mode, i.e. not connecting to CSI driver and marking everything as attached. Expected CSI driver name is \"csi/dummy\".") showVersion = flag.Bool("version", false, "Show version.") @@ -76,6 +76,10 @@ func main() { } klog.Infof("Version: %s", version) + if *connectionTimeout != 0 { + klog.Warningf("Warning: option -connection-timeout is deprecated and has no effect") + } + // Create the client config. Use kubeconfig if given, otherwise assume in-cluster. config, err := buildConfig(*kubeconfig) if err != nil { @@ -106,14 +110,14 @@ func main() { attacher = dummyAttacherName } else { // Connect to CSI. - csiConn, err := connection.New(*csiAddress, *connectionTimeout) + csiConn, err := connection.New(*csiAddress) if err != nil { klog.Error(err.Error()) os.Exit(1) } - // Check it's ready - if err = waitForDriverReady(csiConn, *connectionTimeout); err != nil { + err = csiConn.Probe(*timeout) + if err != nil { klog.Error(err.Error()) os.Exit(1) } @@ -199,25 +203,3 @@ func buildConfig(kubeconfig string) (*rest.Config, error) { } return rest.InClusterConfig() } - -func waitForDriverReady(csiConn connection.CSIConnection, timeout time.Duration) error { - now := time.Now() - finish := now.Add(timeout) - var err error - for { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - err = csiConn.Probe(ctx) - if err == nil { - klog.V(2).Infof("Probe succeeded") - return nil - } - klog.V(2).Infof("Probe failed with %s", err) - - now := time.Now() - if now.After(finish) { - return fmt.Errorf("Failed to probe the controller: %s", err) - } - time.Sleep(time.Second) - } -} diff --git a/pkg/connection/connection.go b/pkg/connection/connection.go index b2c6da0a..35642633 100644 --- a/pkg/connection/connection.go +++ b/pkg/connection/connection.go @@ -18,16 +18,13 @@ package connection import ( "context" - "fmt" - "net" - "strings" "time" "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/kubernetes-csi/csi-lib-utils/connection" "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/connectivity" "google.golang.org/grpc/status" "k8s.io/klog" ) @@ -61,14 +58,15 @@ type CSIConnection interface { Detach(ctx context.Context, volumeID string, nodeID string, secrets map[string]string) (detached bool, err error) // Probe checks that the CSI driver is ready to process requests - Probe(ctx context.Context) error + Probe(singleProbeTimeout time.Duration) error // Close the connection Close() error } type csiConnection struct { - conn *grpc.ClientConn + conn *grpc.ClientConn + capabilities []csi.ControllerServiceCapability } var ( @@ -76,8 +74,8 @@ var ( ) // New provides a new CSI connection object. -func New(address string, timeout time.Duration) (CSIConnection, error) { - conn, err := connect(address, timeout) +func New(address string) (CSIConnection, error) { + conn, err := connection.Connect(address, connection.OnConnectionLoss(connection.ExitOnConnectionLoss())) if err != nil { return nil, err } @@ -86,118 +84,32 @@ func New(address string, timeout time.Duration) (CSIConnection, error) { }, nil } -func connect(address string, timeout time.Duration) (*grpc.ClientConn, error) { - klog.V(2).Infof("Connecting to %s", address) - dialOptions := []grpc.DialOption{ - grpc.WithInsecure(), - grpc.WithBackoffMaxDelay(time.Second), - grpc.WithUnaryInterceptor(logGRPC), - } - if strings.HasPrefix(address, "/") { - dialOptions = append(dialOptions, grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout("unix", addr, timeout) - })) - } - conn, err := grpc.Dial(address, dialOptions...) - - if err != nil { - return nil, err - } - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - for { - if !conn.WaitForStateChange(ctx, conn.GetState()) { - klog.V(4).Infof("Connection timed out") - return conn, nil // return nil, subsequent GetPluginInfo will show the real connection error - } - if conn.GetState() == connectivity.Ready { - klog.V(3).Infof("Connected") - return conn, nil - } - klog.V(4).Infof("Still trying, connection is %s", conn.GetState()) - } -} - func (c *csiConnection) GetDriverName(ctx context.Context) (string, error) { - client := csi.NewIdentityClient(c.conn) - - req := csi.GetPluginInfoRequest{} - - rsp, err := client.GetPluginInfo(ctx, &req) - if err != nil { - return "", err - } - name := rsp.GetName() - if name == "" { - return "", fmt.Errorf("name is empty") - } - return name, nil + return connection.GetDriverName(ctx, c.conn) } -func (c *csiConnection) Probe(ctx context.Context) error { - client := csi.NewIdentityClient(c.conn) - - req := csi.ProbeRequest{} - - _, err := client.Probe(ctx, &req) - if err != nil { - return err - } - return nil +func (c *csiConnection) Probe(singleProbeTimeout time.Duration) error { + return connection.ProbeForever(c.conn, singleProbeTimeout) } func (c *csiConnection) SupportsControllerPublish(ctx context.Context) (supportsControllerPublish bool, supportsPublishReadOnly bool, err error) { - supportsControllerPublish = false - supportsPublishReadOnly = false - - client := csi.NewControllerClient(c.conn) - req := csi.ControllerGetCapabilitiesRequest{} - - rsp, err := client.ControllerGetCapabilities(ctx, &req) + caps, err := connection.GetControllerCapabilities(ctx, c.conn) if err != nil { return false, false, err } - caps := rsp.GetCapabilities() - for _, cap := range caps { - if cap == nil { - continue - } - rpc := cap.GetRpc() - if rpc == nil { - continue - } - if rpc.GetType() == csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME { - supportsControllerPublish = true - } - if rpc.GetType() == csi.ControllerServiceCapability_RPC_PUBLISH_READONLY { - supportsPublishReadOnly = true - } - } + + supportsControllerPublish = caps[csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME] + supportsPublishReadOnly = caps[csi.ControllerServiceCapability_RPC_PUBLISH_READONLY] return supportsControllerPublish, supportsPublishReadOnly, nil } func (c *csiConnection) SupportsPluginControllerService(ctx context.Context) (bool, error) { - client := csi.NewIdentityClient(c.conn) - req := csi.GetPluginCapabilitiesRequest{} - - rsp, err := client.GetPluginCapabilities(ctx, &req) + caps, err := connection.GetPluginCapabilities(ctx, c.conn) if err != nil { return false, err } - caps := rsp.GetCapabilities() - for _, cap := range caps { - if cap == nil { - continue - } - service := cap.GetService() - if service == nil { - continue - } - if service.GetType() == csi.PluginCapability_Service_CONTROLLER_SERVICE { - return true, nil - } - } - return false, nil + + return caps[csi.PluginCapability_Service_CONTROLLER_SERVICE], nil } func (c *csiConnection) Attach(ctx context.Context, volumeID string, readOnly bool, nodeID string, caps *csi.VolumeCapability, context, secrets map[string]string) (metadata map[string]string, detached bool, err error) { From ff489344f57f4b6ab72b342b38813af4524d05e1 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 14 Feb 2019 11:33:40 +0100 Subject: [PATCH 3/3] Fix unit tests --- pkg/connection/connection_test.go | 38 ++++++++++++++++++++++++------- pkg/controller/framework_test.go | 2 +- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/pkg/connection/connection_test.go b/pkg/connection/connection_test.go index 05f76d17..216410d1 100644 --- a/pkg/connection/connection_test.go +++ b/pkg/connection/connection_test.go @@ -19,6 +19,9 @@ package connection import ( "context" "fmt" + "io/ioutil" + "os" + "path/filepath" "reflect" "testing" @@ -52,7 +55,15 @@ func pbMatch(x interface{}) gomock.Matcher { return &pbMatcher{v} } -func createMockServer(t *testing.T) (*gomock.Controller, *driver.MockCSIDriver, *driver.MockIdentityServer, *driver.MockControllerServer, CSIConnection, error) { +func tempDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "external-attacher-test-") + if err != nil { + t.Fatalf("Cannot create temporary directory: %s", err) + } + return dir +} + +func createMockServer(t *testing.T, tmpdir string) (*gomock.Controller, *driver.MockCSIDriver, *driver.MockIdentityServer, *driver.MockControllerServer, CSIConnection, error) { // Start the mock server mockController := gomock.NewController(t) identityServer := driver.NewMockIdentityServer(mockController) @@ -61,11 +72,12 @@ func createMockServer(t *testing.T) (*gomock.Controller, *driver.MockCSIDriver, Identity: identityServer, Controller: controllerServer, }) - drv.Start() + drv.StartOnAddress("unix", filepath.Join(tmpdir, "csi.sock")) // Create a client connection to it addr := drv.Address() - csiConn, err := New(addr, 10) + t.Logf("adds: %s", addr) + csiConn, err := New(addr) if err != nil { return nil, nil, nil, nil, nil, err } @@ -106,7 +118,9 @@ func TestGetPluginInfo(t *testing.T) { }, } - mockController, driver, identityServer, _, csiConn, err := createMockServer(t) + tmpdir := tempDir(t) + defer os.RemoveAll(tmpdir) + mockController, driver, identityServer, _, csiConn, err := createMockServer(t, tmpdir) if err != nil { t.Fatal(err) } @@ -244,7 +258,9 @@ func TestSupportsControllerPublish(t *testing.T) { }, } - mockController, driver, _, controllerServer, csiConn, err := createMockServer(t) + tmpdir := tempDir(t) + defer os.RemoveAll(tmpdir) + mockController, driver, _, controllerServer, csiConn, err := createMockServer(t, tmpdir) if err != nil { t.Fatal(err) } @@ -351,7 +367,9 @@ func TestSupportsPluginControllerService(t *testing.T) { }, } - mockController, driver, identityServer, _, csiConn, err := createMockServer(t) + tmpdir := tempDir(t) + defer os.RemoveAll(tmpdir) + mockController, driver, identityServer, _, csiConn, err := createMockServer(t, tmpdir) if err != nil { t.Fatal(err) } @@ -530,7 +548,9 @@ func TestAttach(t *testing.T) { }, } - mockController, driver, _, controllerServer, csiConn, err := createMockServer(t) + tmpdir := tempDir(t) + defer os.RemoveAll(tmpdir) + mockController, driver, _, controllerServer, csiConn, err := createMockServer(t, tmpdir) if err != nil { t.Fatal(err) } @@ -635,7 +655,9 @@ func TestDetachAttach(t *testing.T) { }, } - mockController, driver, _, controllerServer, csiConn, err := createMockServer(t) + tmpdir := tempDir(t) + defer os.RemoveAll(tmpdir) + mockController, driver, _, controllerServer, csiConn, err := createMockServer(t, tmpdir) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/framework_test.go b/pkg/controller/framework_test.go index 1326707e..83a64c00 100644 --- a/pkg/controller/framework_test.go +++ b/pkg/controller/framework_test.go @@ -452,6 +452,6 @@ func (f *fakeCSIConnection) Close() error { return fmt.Errorf("Not implemented") } -func (f *fakeCSIConnection) Probe(ctx context.Context) error { +func (f *fakeCSIConnection) Probe(timeout time.Duration) error { return nil }