-
Notifications
You must be signed in to change notification settings - Fork 200
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
Move pkg util strings/keymutex from kubernetes kubernetes #55
Changes from 26 commits
0110028
d2fe06d
96c2c2b
8620da4
22f7437
268d9e1
7d203d6
0095d8f
97a3a81
7554c42
7e61e68
6bb1ef6
034b5d4
1883524
02cc29c
7557de2
e18510c
2c582b9
280213a
1b4c91e
19ea2b7
4d41905
fc945b6
58f4cf1
fa5daa7
41bae72
2b39f60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
load( | ||
"@io_bazel_rules_go//go:def.bzl", | ||
"go_library", | ||
"go_test", | ||
) | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = [ | ||
"hashed.go", | ||
"keymutex.go", | ||
], | ||
importpath = "k8s.io/kubernetes/pkg/util/keymutex", | ||
deps = ["//vendor/github.com/golang/glog:go_default_library"], | ||
) | ||
|
||
go_test( | ||
name = "go_default_test", | ||
srcs = ["keymutex_test.go"], | ||
embed = [":go_default_library"], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [":package-srcs"], | ||
tags = ["automanaged"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
Copyright 2018 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 keymutex | ||
|
||
import ( | ||
"hash/fnv" | ||
"runtime" | ||
"sync" | ||
|
||
"github.com/golang/glog" | ||
) | ||
|
||
// NewHashed returns a new instance of KeyMutex which hashes arbitrary keys to | ||
// a fixed set of locks. `n` specifies number of locks, if n <= 0, we use | ||
// number of cpus. | ||
// Note that because it uses fixed set of locks, different keys may share same | ||
// lock, so it's possible to wait on same lock. | ||
func NewHashed(n int) KeyMutex { | ||
if n <= 0 { | ||
n = runtime.NumCPU() | ||
} | ||
return &hashedKeyMutex{ | ||
mutexes: make([]sync.Mutex, n), | ||
} | ||
} | ||
|
||
type hashedKeyMutex struct { | ||
mutexes []sync.Mutex | ||
} | ||
|
||
// Acquires a lock associated with the specified ID. | ||
func (km *hashedKeyMutex) LockKey(id string) { | ||
glog.V(5).Infof("hashedKeyMutex.LockKey(...) called for id %q\r\n", id) | ||
km.mutexes[km.hash(id)%len(km.mutexes)].Lock() | ||
glog.V(5).Infof("hashedKeyMutex.LockKey(...) for id %q completed.\r\n", id) | ||
} | ||
|
||
// Releases the lock associated with the specified ID. | ||
func (km *hashedKeyMutex) UnlockKey(id string) error { | ||
glog.V(5).Infof("hashedKeyMutex.UnlockKey(...) called for id %q\r\n", id) | ||
km.mutexes[km.hash(id)%len(km.mutexes)].Unlock() | ||
glog.V(5).Infof("hashedKeyMutex.UnlockKey(...) for id %q completed.\r\n", id) | ||
return nil | ||
} | ||
|
||
func (km *hashedKeyMutex) hash(id string) int { | ||
h := fnv.New32a() | ||
h.Write([]byte(id)) | ||
return int(h.Sum32()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
Copyright 2015 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 keymutex | ||
|
||
// KeyMutex is a thread-safe interface for acquiring locks on arbitrary strings. | ||
type KeyMutex interface { | ||
// Acquires a lock associated with the specified ID, creates the lock if one doesn't already exist. | ||
LockKey(id string) | ||
|
||
// Releases the lock associated with the specified ID. | ||
// Returns an error if the specified ID doesn't exist. | ||
UnlockKey(id string) error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
Copyright 2015 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 keymutex | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
const ( | ||
callbackTimeout = 1 * time.Second | ||
) | ||
|
||
func newKeyMutexes() []KeyMutex { | ||
return []KeyMutex{ | ||
NewHashed(0), | ||
NewHashed(1), | ||
NewHashed(2), | ||
NewHashed(4), | ||
} | ||
} | ||
|
||
func Test_SingleLock_NoUnlock(t *testing.T) { | ||
for _, km := range newKeyMutexes() { | ||
// Arrange | ||
key := "fakeid" | ||
callbackCh := make(chan interface{}) | ||
|
||
// Act | ||
go lockAndCallback(km, key, callbackCh) | ||
|
||
// Assert | ||
verifyCallbackHappens(t, callbackCh) | ||
} | ||
} | ||
|
||
func Test_SingleLock_SingleUnlock(t *testing.T) { | ||
for _, km := range newKeyMutexes() { | ||
// Arrange | ||
key := "fakeid" | ||
callbackCh := make(chan interface{}) | ||
|
||
// Act & Assert | ||
go lockAndCallback(km, key, callbackCh) | ||
verifyCallbackHappens(t, callbackCh) | ||
km.UnlockKey(key) | ||
} | ||
} | ||
|
||
func Test_DoubleLock_DoubleUnlock(t *testing.T) { | ||
for _, km := range newKeyMutexes() { | ||
// Arrange | ||
key := "fakeid" | ||
callbackCh1stLock := make(chan interface{}) | ||
callbackCh2ndLock := make(chan interface{}) | ||
|
||
// Act & Assert | ||
go lockAndCallback(km, key, callbackCh1stLock) | ||
verifyCallbackHappens(t, callbackCh1stLock) | ||
go lockAndCallback(km, key, callbackCh2ndLock) | ||
verifyCallbackDoesntHappens(t, callbackCh2ndLock) | ||
km.UnlockKey(key) | ||
verifyCallbackHappens(t, callbackCh2ndLock) | ||
km.UnlockKey(key) | ||
} | ||
} | ||
|
||
func lockAndCallback(km KeyMutex, id string, callbackCh chan<- interface{}) { | ||
km.LockKey(id) | ||
callbackCh <- true | ||
} | ||
|
||
func verifyCallbackHappens(t *testing.T, callbackCh <-chan interface{}) bool { | ||
select { | ||
case <-callbackCh: | ||
return true | ||
case <-time.After(callbackTimeout): | ||
t.Fatalf("Timed out waiting for callback.") | ||
return false | ||
} | ||
} | ||
|
||
func verifyCallbackDoesntHappens(t *testing.T, callbackCh <-chan interface{}) bool { | ||
select { | ||
case <-callbackCh: | ||
t.Fatalf("Unexpected callback.") | ||
return false | ||
case <-time.After(callbackTimeout): | ||
return true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
load( | ||
"@io_bazel_rules_go//go:def.bzl", | ||
"go_library", | ||
"go_test", | ||
) | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = [ | ||
"escape.go", | ||
"line_delimiter.go", | ||
"strings.go", | ||
], | ||
importpath = "k8s.io/kubernetes/pkg/util/strings", | ||
) | ||
|
||
go_test( | ||
name = "go_default_test", | ||
srcs = [ | ||
"escape_test.go", | ||
"line_delimiter_test.go", | ||
"strings_test.go", | ||
], | ||
embed = [":go_default_library"], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [":package-srcs"], | ||
tags = ["automanaged"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
Copyright 2014 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 strings | ||
|
||
import ( | ||
"strings" | ||
) | ||
|
||
// EscapePluginName converts a plugin name in the format | ||
// vendor/pluginname into a proper ondisk vendor~pluginname plugin directory | ||
// format. | ||
func EscapePluginName(in string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I swear I made this comment before, but can't find it. The Escape/UnescapePluginName funcs are totally redundant. Can we get rid of them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped! (Escape|Unescape)PluginName |
||
return strings.Replace(in, "/", "~", -1) | ||
} | ||
|
||
// UnescapePluginName converts a plugin directory name in the format | ||
// vendor~pluginname into a proper vendor/pluginname. | ||
func UnescapePluginName(in string) string { | ||
return strings.Replace(in, "~", "/", -1) | ||
} | ||
|
||
// EscapeQualifiedName converts a plugin name, which might contain a / into a | ||
// string that is safe to use on-disk. This assumes that the input has already | ||
// been validates as a qualified name. we use "~" rather than ":" here in case | ||
// we ever use a filesystem that doesn't allow ":". | ||
func EscapeQualifiedName(in string) string { | ||
return strings.Replace(in, "/", "~", -1) | ||
} | ||
|
||
// UnescapeQualifiedName converts an escaped plugin name (as per EscapeQualifiedName) | ||
// back to its normal form. This assumes that the input has already been | ||
// validates as a qualified name. | ||
func UnescapeQualifiedName(in string) string { | ||
return strings.Replace(in, "~", "/", -1) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
Copyright 2016 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 strings | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestEscapePluginName(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
output string | ||
}{ | ||
{"kubernetes.io/blah", "kubernetes.io~blah"}, | ||
{"blah/blerg/borg", "blah~blerg~borg"}, | ||
{"kubernetes.io", "kubernetes.io"}, | ||
} | ||
for i, tc := range testCases { | ||
escapee := EscapePluginName(tc.input) | ||
if escapee != tc.output { | ||
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.output, escapee) | ||
} | ||
original := UnescapePluginName(escapee) | ||
if original != tc.input { | ||
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.input, original) | ||
} | ||
} | ||
} | ||
|
||
func TestEscapeQualifiedNameForDisk(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
output string | ||
}{ | ||
{"kubernetes.io/blah", "kubernetes.io~blah"}, | ||
{"blah/blerg/borg", "blah~blerg~borg"}, | ||
{"kubernetes.io", "kubernetes.io"}, | ||
} | ||
for i, tc := range testCases { | ||
escapee := EscapeQualifiedName(tc.input) | ||
if escapee != tc.output { | ||
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.output, escapee) | ||
} | ||
original := UnescapeQualifiedName(escapee) | ||
if original != tc.input { | ||
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.input, original) | ||
} | ||
} | ||
} |
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.
Does golint pass on this package? I thought it complained about comments not starting with function names. New code should be golint clean (he says, realizing now that he forgot to check for his PRs).
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.
travis seems to test at least gofmt and vet. though not go lint. if we want to do golint, we should start by adding it there there and not start on this PR
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.
That's fair. I need to do it with my merged code as well so I'll raise a PR with those changes independent of this one.
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.
Getting clean lint and vet should be a requirement for packages promoted to util