Skip to content
This repository was archived by the owner on Mar 22, 2018. It is now read-only.

Commit 0c7c9f1

Browse files
committed
Merge branch 'master' of github.com:kubernetes/kubernetes into branch1
2 parents fc99c7a + 7f023c2 commit 0c7c9f1

5 files changed

Lines changed: 331 additions & 14 deletions

File tree

pkg/cloudprovider/providers/openstack/openstack_client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (os *OpenStack) NewNetworkV2() (*gophercloud.ServiceClient, error) {
2828
Region: os.region,
2929
})
3030
if err != nil {
31-
glog.Warningf("Failed to find network v2 endpoint: %v", err)
31+
glog.Warningf("Failed to find network v2 endpoint for region %s: %v", os.region, err)
3232
return nil, err
3333
}
3434
return network, nil
@@ -39,7 +39,7 @@ func (os *OpenStack) NewComputeV2() (*gophercloud.ServiceClient, error) {
3939
Region: os.region,
4040
})
4141
if err != nil {
42-
glog.Warningf("Failed to find compute v2 endpoint: %v", err)
42+
glog.Warningf("Failed to find compute v2 endpoint for region %s: %v", os.region, err)
4343
return nil, err
4444
}
4545
return compute, nil
@@ -50,7 +50,7 @@ func (os *OpenStack) NewBlockStorageV1() (*gophercloud.ServiceClient, error) {
5050
Region: os.region,
5151
})
5252
if err != nil {
53-
glog.Errorf("Unable to initialize cinder v1 client for region: %s", os.region)
53+
glog.Errorf("Unable to initialize cinder v1 client for region %s: %v", os.region, err)
5454
return nil, err
5555
}
5656
return storage, nil
@@ -61,7 +61,7 @@ func (os *OpenStack) NewBlockStorageV2() (*gophercloud.ServiceClient, error) {
6161
Region: os.region,
6262
})
6363
if err != nil {
64-
glog.Errorf("Unable to initialize cinder v2 client for region: %s", os.region)
64+
glog.Errorf("Unable to initialize cinder v2 client for region %s: %v", os.region, err)
6565
return nil, err
6666
}
6767
return storage, nil

pkg/cloudprovider/providers/openstack/openstack_volumes.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,7 @@ func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) {
215215
if err != nil {
216216
return "", err
217217
}
218-
if volume.Status != VolumeAvailableStatus {
219-
errmsg := fmt.Sprintf("volume %s status is %s, not %s, can not be attached to instance %s.", volume.Name, volume.Status, VolumeAvailableStatus, instanceID)
220-
glog.Errorf(errmsg)
221-
return "", errors.New(errmsg)
222-
}
218+
223219
cClient, err := os.NewComputeV2()
224220
if err != nil {
225221
return "", err
@@ -230,11 +226,9 @@ func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) {
230226
glog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
231227
return volume.ID, nil
232228
}
233-
glog.V(2).Infof("Disk %s is attached to a different instance (%s), detaching", volumeID, volume.AttachedServerId)
234-
err = os.DetachDisk(volume.AttachedServerId, volumeID)
235-
if err != nil {
236-
return "", err
237-
}
229+
errmsg := fmt.Sprintf("Disk %s is attached to a different instance (%s)", volumeID, volume.AttachedServerId)
230+
glog.V(2).Infof(errmsg)
231+
return "", errors.New(errmsg)
238232
}
239233

240234
startTime := time.Now()
@@ -258,6 +252,12 @@ func (os *OpenStack) DetachDisk(instanceID, volumeID string) error {
258252
if err != nil {
259253
return err
260254
}
255+
if volume.Status == VolumeAvailableStatus {
256+
// "available" is fine since that means the volume is detached from instance already.
257+
glog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
258+
return nil
259+
}
260+
261261
if volume.Status != VolumeInUseStatus {
262262
errmsg := fmt.Sprintf("can not detach volume %s, its status is %s.", volume.Name, volume.Status)
263263
glog.Errorf(errmsg)
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+
licenses(["notice"])
4+
5+
load(
6+
"@io_bazel_rules_go//go:def.bzl",
7+
"go_library",
8+
"go_test",
9+
)
10+
11+
go_test(
12+
name = "go_default_test",
13+
srcs = ["openstack_test.go"],
14+
library = ":go_default_library",
15+
tags = ["automanaged"],
16+
)
17+
18+
go_library(
19+
name = "go_default_library",
20+
srcs = ["openstack.go"],
21+
tags = ["automanaged"],
22+
deps = [
23+
"//vendor/github.com/golang/glog:go_default_library",
24+
"//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library",
25+
"//vendor/k8s.io/client-go/rest:go_default_library",
26+
],
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+
)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
Copyright 2017 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 openstack
18+
19+
import (
20+
"fmt"
21+
"net/http"
22+
"sync"
23+
"time"
24+
25+
"github.com/golang/glog"
26+
"github.com/gophercloud/gophercloud/openstack"
27+
28+
restclient "k8s.io/client-go/rest"
29+
)
30+
31+
func init() {
32+
if err := restclient.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil {
33+
glog.Fatalf("Failed to register openstack auth plugin: %s", err)
34+
}
35+
}
36+
37+
// DefaultTTLDuration is the time before a token gets expired.
38+
const DefaultTTLDuration = 10 * time.Minute
39+
40+
// openstackAuthProvider is an authprovider for openstack. this provider reads
41+
// the environment variables to determine the client identity, and generates a
42+
// token which will be inserted into the request header later.
43+
type openstackAuthProvider struct {
44+
ttl time.Duration
45+
46+
tokenGetter TokenGetter
47+
}
48+
49+
// TokenGetter returns a bearer token that can be inserted into request.
50+
type TokenGetter interface {
51+
Token() (string, error)
52+
}
53+
54+
type tokenGetter struct{}
55+
56+
// Token creates a token by authenticate with keystone.
57+
func (*tokenGetter) Token() (string, error) {
58+
options, err := openstack.AuthOptionsFromEnv()
59+
if err != nil {
60+
return "", fmt.Errorf("failed to read openstack env vars: %s", err)
61+
}
62+
client, err := openstack.AuthenticatedClient(options)
63+
if err != nil {
64+
return "", fmt.Errorf("authentication failed: %s", err)
65+
}
66+
return client.TokenID, nil
67+
}
68+
69+
// cachedGetter caches a token until it gets expired, after the expiration, it will
70+
// generate another token and cache it.
71+
type cachedGetter struct {
72+
mutex sync.Mutex
73+
tokenGetter TokenGetter
74+
75+
token string
76+
born time.Time
77+
ttl time.Duration
78+
}
79+
80+
// Token returns the current available token, create a new one if expired.
81+
func (c *cachedGetter) Token() (string, error) {
82+
c.mutex.Lock()
83+
defer c.mutex.Unlock()
84+
85+
var err error
86+
// no token or exceeds the TTL
87+
if c.token == "" || time.Now().Sub(c.born) > c.ttl {
88+
c.token, err = c.tokenGetter.Token()
89+
if err != nil {
90+
return "", fmt.Errorf("failed to get token: %s", err)
91+
}
92+
c.born = time.Now()
93+
}
94+
return c.token, nil
95+
}
96+
97+
// tokenRoundTripper implements the RoundTripper interface: adding the bearer token
98+
// into the request header.
99+
type tokenRoundTripper struct {
100+
http.RoundTripper
101+
102+
tokenGetter TokenGetter
103+
}
104+
105+
// RoundTrip adds the bearer token into the request.
106+
func (t *tokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
107+
// if the authorization header already present, use it.
108+
if req.Header.Get("Authorization") != "" {
109+
return t.RoundTripper.RoundTrip(req)
110+
}
111+
112+
token, err := t.tokenGetter.Token()
113+
if err == nil {
114+
req.Header.Set("Authorization", "Bearer "+token)
115+
} else {
116+
glog.V(4).Infof("failed to get token: %s", err)
117+
}
118+
119+
return t.RoundTripper.RoundTrip(req)
120+
}
121+
122+
// newOpenstackAuthProvider creates an auth provider which works with openstack
123+
// environment.
124+
func newOpenstackAuthProvider(clusterAddress string, config map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
125+
var ttlDuration time.Duration
126+
var err error
127+
128+
ttl, found := config["ttl"]
129+
if !found {
130+
ttlDuration = DefaultTTLDuration
131+
// persist to config
132+
config["ttl"] = ttlDuration.String()
133+
if err = persister.Persist(config); err != nil {
134+
return nil, fmt.Errorf("failed to persist config: %s", err)
135+
}
136+
} else {
137+
ttlDuration, err = time.ParseDuration(ttl)
138+
if err != nil {
139+
return nil, fmt.Errorf("failed to parse ttl config: %s", err)
140+
}
141+
}
142+
143+
// TODO: read/persist client configuration(OS_XXX env vars) in config
144+
145+
return &openstackAuthProvider{
146+
ttl: ttlDuration,
147+
tokenGetter: &tokenGetter{},
148+
}, nil
149+
}
150+
151+
func (oap *openstackAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
152+
return &tokenRoundTripper{
153+
RoundTripper: rt,
154+
tokenGetter: &cachedGetter{
155+
tokenGetter: oap.tokenGetter,
156+
ttl: oap.ttl,
157+
},
158+
}
159+
}
160+
161+
func (oap *openstackAuthProvider) Login() error { return nil }
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
Copyright 2017 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 openstack
18+
19+
import (
20+
"math/rand"
21+
"net/http"
22+
"testing"
23+
"time"
24+
)
25+
26+
// testTokenGetter is a simple random token getter.
27+
type testTokenGetter struct{}
28+
29+
const LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
30+
31+
func RandStringBytes(n int) string {
32+
b := make([]byte, n)
33+
for i := range b {
34+
b[i] = LetterBytes[rand.Intn(len(LetterBytes))]
35+
}
36+
return string(b)
37+
}
38+
39+
func (*testTokenGetter) Token() (string, error) {
40+
return RandStringBytes(32), nil
41+
}
42+
43+
// testRoundTripper is mocked roundtripper which responds with unauthorized when
44+
// there is no authorization header, otherwise returns status ok.
45+
type testRoundTripper struct{}
46+
47+
func (trt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
48+
authHeader := req.Header.Get("Authorization")
49+
if authHeader == "" || authHeader == "Bearer " {
50+
return &http.Response{
51+
StatusCode: http.StatusUnauthorized,
52+
}, nil
53+
}
54+
return &http.Response{StatusCode: http.StatusOK}, nil
55+
}
56+
57+
func TestOpenstackAuthProvider(t *testing.T) {
58+
trt := &tokenRoundTripper{
59+
RoundTripper: &testRoundTripper{},
60+
}
61+
62+
tests := []struct {
63+
name string
64+
ttl time.Duration
65+
interval time.Duration
66+
same bool
67+
}{
68+
{
69+
name: "normal",
70+
ttl: 2 * time.Second,
71+
interval: 1 * time.Second,
72+
same: true,
73+
},
74+
{
75+
name: "expire",
76+
ttl: 1 * time.Second,
77+
interval: 2 * time.Second,
78+
same: false,
79+
},
80+
}
81+
82+
for _, test := range tests {
83+
trt.tokenGetter = &cachedGetter{
84+
tokenGetter: &testTokenGetter{},
85+
ttl: test.ttl,
86+
}
87+
88+
req, err := http.NewRequest(http.MethodPost, "https://test-api-server.com", nil)
89+
if err != nil {
90+
t.Errorf("failed to new request: %s", err)
91+
}
92+
trt.RoundTrip(req)
93+
header := req.Header.Get("Authorization")
94+
if header == "" {
95+
t.Errorf("expect to see token in header, but is absent")
96+
}
97+
98+
time.Sleep(test.interval)
99+
100+
req, err = http.NewRequest(http.MethodPost, "https://test-api-server.com", nil)
101+
if err != nil {
102+
t.Errorf("failed to new request: %s", err)
103+
}
104+
trt.RoundTrip(req)
105+
newHeader := req.Header.Get("Authorization")
106+
if newHeader == "" {
107+
t.Errorf("expect to see token in header, but is absent")
108+
}
109+
110+
same := newHeader == header
111+
if same != test.same {
112+
t.Errorf("expect to get %t when compare header, but saw %t", test.same, same)
113+
}
114+
}
115+
116+
}

0 commit comments

Comments
 (0)