Skip to content

Commit fe7be10

Browse files
authored
Merge pull request #55 from dims/move-pkg-util-strings-from-kubernetes-kubernetes
Move pkg util strings/keymutex from kubernetes kubernetes
2 parents 41e9742 + 2b39f60 commit fe7be10

File tree

11 files changed

+573
-0
lines changed

11 files changed

+573
-0
lines changed

keymutex/BUILD

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load(
4+
"@io_bazel_rules_go//go:def.bzl",
5+
"go_library",
6+
"go_test",
7+
)
8+
9+
go_library(
10+
name = "go_default_library",
11+
srcs = [
12+
"hashed.go",
13+
"keymutex.go",
14+
],
15+
importpath = "k8s.io/kubernetes/pkg/util/keymutex",
16+
deps = ["//vendor/github.com/golang/glog:go_default_library"],
17+
)
18+
19+
go_test(
20+
name = "go_default_test",
21+
srcs = ["keymutex_test.go"],
22+
embed = [":go_default_library"],
23+
)
24+
25+
filegroup(
26+
name = "package-srcs",
27+
srcs = glob(["**"]),
28+
tags = ["automanaged"],
29+
visibility = ["//visibility:private"],
30+
)
31+
32+
filegroup(
33+
name = "all-srcs",
34+
srcs = [":package-srcs"],
35+
tags = ["automanaged"],
36+
)

keymutex/hashed.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package keymutex
18+
19+
import (
20+
"hash/fnv"
21+
"runtime"
22+
"sync"
23+
24+
"github.com/golang/glog"
25+
)
26+
27+
// NewHashed returns a new instance of KeyMutex which hashes arbitrary keys to
28+
// a fixed set of locks. `n` specifies number of locks, if n <= 0, we use
29+
// number of cpus.
30+
// Note that because it uses fixed set of locks, different keys may share same
31+
// lock, so it's possible to wait on same lock.
32+
func NewHashed(n int) KeyMutex {
33+
if n <= 0 {
34+
n = runtime.NumCPU()
35+
}
36+
return &hashedKeyMutex{
37+
mutexes: make([]sync.Mutex, n),
38+
}
39+
}
40+
41+
type hashedKeyMutex struct {
42+
mutexes []sync.Mutex
43+
}
44+
45+
// Acquires a lock associated with the specified ID.
46+
func (km *hashedKeyMutex) LockKey(id string) {
47+
glog.V(5).Infof("hashedKeyMutex.LockKey(...) called for id %q\r\n", id)
48+
km.mutexes[km.hash(id)%len(km.mutexes)].Lock()
49+
glog.V(5).Infof("hashedKeyMutex.LockKey(...) for id %q completed.\r\n", id)
50+
}
51+
52+
// Releases the lock associated with the specified ID.
53+
func (km *hashedKeyMutex) UnlockKey(id string) error {
54+
glog.V(5).Infof("hashedKeyMutex.UnlockKey(...) called for id %q\r\n", id)
55+
km.mutexes[km.hash(id)%len(km.mutexes)].Unlock()
56+
glog.V(5).Infof("hashedKeyMutex.UnlockKey(...) for id %q completed.\r\n", id)
57+
return nil
58+
}
59+
60+
func (km *hashedKeyMutex) hash(id string) int {
61+
h := fnv.New32a()
62+
h.Write([]byte(id))
63+
return int(h.Sum32())
64+
}

keymutex/keymutex.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2015 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package keymutex
18+
19+
// KeyMutex is a thread-safe interface for acquiring locks on arbitrary strings.
20+
type KeyMutex interface {
21+
// Acquires a lock associated with the specified ID, creates the lock if one doesn't already exist.
22+
LockKey(id string)
23+
24+
// Releases the lock associated with the specified ID.
25+
// Returns an error if the specified ID doesn't exist.
26+
UnlockKey(id string) error
27+
}

keymutex/keymutex_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2015 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package keymutex
18+
19+
import (
20+
"testing"
21+
"time"
22+
)
23+
24+
const (
25+
callbackTimeout = 1 * time.Second
26+
)
27+
28+
func newKeyMutexes() []KeyMutex {
29+
return []KeyMutex{
30+
NewHashed(0),
31+
NewHashed(1),
32+
NewHashed(2),
33+
NewHashed(4),
34+
}
35+
}
36+
37+
func Test_SingleLock_NoUnlock(t *testing.T) {
38+
for _, km := range newKeyMutexes() {
39+
// Arrange
40+
key := "fakeid"
41+
callbackCh := make(chan interface{})
42+
43+
// Act
44+
go lockAndCallback(km, key, callbackCh)
45+
46+
// Assert
47+
verifyCallbackHappens(t, callbackCh)
48+
}
49+
}
50+
51+
func Test_SingleLock_SingleUnlock(t *testing.T) {
52+
for _, km := range newKeyMutexes() {
53+
// Arrange
54+
key := "fakeid"
55+
callbackCh := make(chan interface{})
56+
57+
// Act & Assert
58+
go lockAndCallback(km, key, callbackCh)
59+
verifyCallbackHappens(t, callbackCh)
60+
km.UnlockKey(key)
61+
}
62+
}
63+
64+
func Test_DoubleLock_DoubleUnlock(t *testing.T) {
65+
for _, km := range newKeyMutexes() {
66+
// Arrange
67+
key := "fakeid"
68+
callbackCh1stLock := make(chan interface{})
69+
callbackCh2ndLock := make(chan interface{})
70+
71+
// Act & Assert
72+
go lockAndCallback(km, key, callbackCh1stLock)
73+
verifyCallbackHappens(t, callbackCh1stLock)
74+
go lockAndCallback(km, key, callbackCh2ndLock)
75+
verifyCallbackDoesntHappens(t, callbackCh2ndLock)
76+
km.UnlockKey(key)
77+
verifyCallbackHappens(t, callbackCh2ndLock)
78+
km.UnlockKey(key)
79+
}
80+
}
81+
82+
func lockAndCallback(km KeyMutex, id string, callbackCh chan<- interface{}) {
83+
km.LockKey(id)
84+
callbackCh <- true
85+
}
86+
87+
func verifyCallbackHappens(t *testing.T, callbackCh <-chan interface{}) bool {
88+
select {
89+
case <-callbackCh:
90+
return true
91+
case <-time.After(callbackTimeout):
92+
t.Fatalf("Timed out waiting for callback.")
93+
return false
94+
}
95+
}
96+
97+
func verifyCallbackDoesntHappens(t *testing.T, callbackCh <-chan interface{}) bool {
98+
select {
99+
case <-callbackCh:
100+
t.Fatalf("Unexpected callback.")
101+
return false
102+
case <-time.After(callbackTimeout):
103+
return true
104+
}
105+
}

strings/BUILD

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load(
4+
"@io_bazel_rules_go//go:def.bzl",
5+
"go_library",
6+
"go_test",
7+
)
8+
9+
go_library(
10+
name = "go_default_library",
11+
srcs = [
12+
"escape.go",
13+
"line_delimiter.go",
14+
"strings.go",
15+
],
16+
importpath = "k8s.io/kubernetes/pkg/util/strings",
17+
)
18+
19+
go_test(
20+
name = "go_default_test",
21+
srcs = [
22+
"escape_test.go",
23+
"line_delimiter_test.go",
24+
"strings_test.go",
25+
],
26+
embed = [":go_default_library"],
27+
)
28+
29+
filegroup(
30+
name = "package-srcs",
31+
srcs = glob(["**"]),
32+
tags = ["automanaged"],
33+
visibility = ["//visibility:private"],
34+
)
35+
36+
filegroup(
37+
name = "all-srcs",
38+
srcs = [":package-srcs"],
39+
tags = ["automanaged"],
40+
)

strings/escape.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2014 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package strings
18+
19+
import (
20+
"strings"
21+
)
22+
23+
// EscapeQualifiedName converts a plugin name, which might contain a / into a
24+
// string that is safe to use on-disk. This assumes that the input has already
25+
// been validates as a qualified name. we use "~" rather than ":" here in case
26+
// we ever use a filesystem that doesn't allow ":".
27+
func EscapeQualifiedName(in string) string {
28+
return strings.Replace(in, "/", "~", -1)
29+
}
30+
31+
// UnescapeQualifiedName converts an escaped plugin name (as per EscapeQualifiedName)
32+
// back to its normal form. This assumes that the input has already been
33+
// validates as a qualified name.
34+
func UnescapeQualifiedName(in string) string {
35+
return strings.Replace(in, "~", "/", -1)
36+
}

strings/escape_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2016 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package strings
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestEscapeQualifiedNameForDisk(t *testing.T) {
24+
testCases := []struct {
25+
input string
26+
output string
27+
}{
28+
{"kubernetes.io/blah", "kubernetes.io~blah"},
29+
{"blah/blerg/borg", "blah~blerg~borg"},
30+
{"kubernetes.io", "kubernetes.io"},
31+
}
32+
for i, tc := range testCases {
33+
escapee := EscapeQualifiedName(tc.input)
34+
if escapee != tc.output {
35+
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.output, escapee)
36+
}
37+
original := UnescapeQualifiedName(escapee)
38+
if original != tc.input {
39+
t.Errorf("case[%d]: expected (%q), got (%q)", i, tc.input, original)
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)