From 201bac1b63f8582012a0e0b480f4bbca0c6d60d9 Mon Sep 17 00:00:00 2001 From: lijuncloud Date: Mon, 6 Aug 2018 19:55:05 +0800 Subject: [PATCH 1/4] Adds an API to return Availability Zones for pools --- examples/policy.json | 5 +++-- openapi-spec/swagger.yaml | 19 +++++++++++++++++++ pkg/api/pool.go | 27 +++++++++++++++++++++++++++ pkg/api/pool_test.go | 16 ++++++++++++++++ pkg/api/router.go | 1 + pkg/db/db.go | 2 ++ pkg/db/drivers/etcd/etcd.go | 27 +++++++++++++++++++++++++++ pkg/db/drivers/etcd/etcd_test.go | 11 +++++++++++ pkg/utils/utils.go | 18 ++++++++++++++++++ testutils/db/fake.go | 10 ++++++++++ testutils/db/testing/mock_db.go | 25 +++++++++++++++++++++++++ 11 files changed, 159 insertions(+), 2 deletions(-) diff --git a/examples/policy.json b/examples/policy.json index 146216d97..337b6799c 100644 --- a/examples/policy.json +++ b/examples/policy.json @@ -45,5 +45,6 @@ "volume_group:list": "rule:admin_or_owner", "volume_group:get": "rule:admin_or_owner", "volume_group:update": "rule:admin_or_owner", - "volume_group:delete": "rule:admin_or_owner" -} + "volume_group:delete": "rule:admin_or_owner", + "availability_zone:list":"" +} \ No newline at end of file diff --git a/openapi-spec/swagger.yaml b/openapi-spec/swagger.yaml index 50c5f4e2f..ca6eb1456 100755 --- a/openapi-spec/swagger.yaml +++ b/openapi-spec/swagger.yaml @@ -141,6 +141,25 @@ paths: description: Forbidden '500': $ref: '#/responses/HTTPStatus500' + '/v1beta/{projectId}/availabilityZones': + parameters: + - $ref: '#/parameters/projectId' + get: + tags: + - Availability Zone + description: Lists availability zones. + responses: + '200': + description: OK + schema: + type: array + items:string + '401': + description: Unauthorized + '403': + description: Forbidden + '500': + $ref: '#/responses/HTTPStatus500' '/v1beta/{projectId}/pools/{poolId}': parameters: - $ref: '#/parameters/projectId' diff --git a/pkg/api/pool.go b/pkg/api/pool.go index bcf6b939d..33a99a955 100755 --- a/pkg/api/pool.go +++ b/pkg/api/pool.go @@ -34,6 +34,33 @@ type PoolPortal struct { BasePortal } +func (this *PoolPortal) ListAvailabilityZones() { + if !policy.Authorize(this.Ctx, "availability_zone:list") { + return + } + azs, err := db.C.ListAvailabilityZones(c.GetContext(this.Ctx)) + if err != nil { + reason := fmt.Sprintf("Get AvailabilityZones for pools failed: %s", err.Error()) + this.Ctx.Output.SetStatus(model.ErrorBadRequest) + this.Ctx.Output.Body(model.ErrorBadRequestStatus(reason)) + log.Error(reason) + return + } + + body, err := json.Marshal(azs) + if err != nil { + reason := fmt.Sprintf("Marshal AvailabilityZones failed: %s", err.Error()) + this.Ctx.Output.SetStatus(model.ErrorInternalServer) + this.Ctx.Output.Body(model.ErrorInternalServerStatus(reason)) + log.Error(reason) + return + } + + this.Ctx.Output.SetStatus(StatusOK) + this.Ctx.Output.Body(body) + return +} + func (this *PoolPortal) ListPools() { if !policy.Authorize(this.Ctx, "pool:list") { return diff --git a/pkg/api/pool_test.go b/pkg/api/pool_test.go index 50d243be1..f66e2824c 100755 --- a/pkg/api/pool_test.go +++ b/pkg/api/pool_test.go @@ -66,6 +66,22 @@ var ( fakePools = []*model.StoragePoolSpec{fakePool} ) +func TestListAvailabilityZones(t *testing.T) { + mockClient := new(dbtest.MockClient) + mockClient.On("ListAvailabilityZones", c.NewAdminContext()).Return(fakePools, nil) + db.C = mockClient + + r, _ := http.NewRequest("GET", "/v1beta/availabilityZones", nil) + w := httptest.NewRecorder() + beego.BeeApp.Handlers.ServeHTTP(w, r) + + expectedZones := "unknow" + t.Log(w) + if !strings.Contains(string(w.Body.Bytes()), expectedZones) { + t.Errorf("Expected %v, actual %v", expectedZones, w.Body.Bytes()) + } +} + func TestListPools(t *testing.T) { mockClient := new(dbtest.MockClient) diff --git a/pkg/api/router.go b/pkg/api/router.go index 5f73ea7b7..13da63738 100755 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -68,6 +68,7 @@ func Run(host string) { // ListPools and GetPool are used for checking the status of backend pool, admin only beego.NSRouter("/:tenantId/pools", &PoolPortal{}, "get:ListPools"), beego.NSRouter("/:tenantId/pools/:poolId", &PoolPortal{}, "get:GetPool"), + beego.NSRouter("/:tenantId/availabilityZones", &PoolPortal{}, "get:ListAvailabilityZones"), beego.NSNamespace("/:tenantId/block", diff --git a/pkg/db/db.go b/pkg/db/db.go index 66e32a0b8..a15750464 100755 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -73,6 +73,8 @@ type Client interface { GetPool(ctx *c.Context, polID string) (*model.StoragePoolSpec, error) + ListAvailabilityZones(ctx *c.Context) ([]string, error) + ListPools(ctx *c.Context) ([]*model.StoragePoolSpec, error) ListPoolsWithFilter(ctx *c.Context, m map[string][]string) ([]*model.StoragePoolSpec, error) diff --git a/pkg/db/drivers/etcd/etcd.go b/pkg/db/drivers/etcd/etcd.go index 2c036f17c..d3b720437 100755 --- a/pkg/db/drivers/etcd/etcd.go +++ b/pkg/db/drivers/etcd/etcd.go @@ -579,6 +579,33 @@ func (c *Client) GetPool(ctx *c.Context, polID string) (*model.StoragePoolSpec, return pol, nil } +//ListAvailabilityZones +func (c *Client) ListAvailabilityZones(ctx *c.Context) ([]string, error) { + dbReq := &Request{ + Url: urls.GeneratePoolURL(urls.Etcd, ""), + } + dbRes := c.List(dbReq) + if dbRes.Status != "Success" { + log.Error("Failed to get AZ for pools in db:", dbRes.Error) + return nil, errors.New(dbRes.Error) + } + var azs = []string{} + if len(dbRes.Message) == 0 { + return azs, nil + } + for _, msg := range dbRes.Message { + var pol = &model.StoragePoolSpec{} + if err := json.Unmarshal([]byte(msg), pol); err != nil { + log.Error("When parsing pool in db:", dbRes.Error) + return nil, errors.New(dbRes.Error) + } + azs = append(azs, pol.AvailabilityZone) + } + //remove redundant AZ + azs = utils.RvRepElement(azs) + return azs, nil +} + // ListPools func (c *Client) ListPools(ctx *c.Context) ([]*model.StoragePoolSpec, error) { dbReq := &Request{ diff --git a/pkg/db/drivers/etcd/etcd_test.go b/pkg/db/drivers/etcd/etcd_test.go index a6a837022..95d5fbcaa 100644 --- a/pkg/db/drivers/etcd/etcd_test.go +++ b/pkg/db/drivers/etcd/etcd_test.go @@ -262,6 +262,17 @@ func TestListDocks(t *testing.T) { } } +func TestListAvailabilityZones(t *testing.T) { + azs, err := fc.ListAvailabilityZones(c.NewAdminContext()) + if err != nil { + t.Error("List pools failed:", err) + } + expected := SamplePools[0].AvailabilityZone + if !reflect.DeepEqual(azs[0], expected) { + t.Errorf("Expected %+v, got %+v\n", expected, azs[0]) + } +} + func TestListPools(t *testing.T) { m := map[string][]string{ "offset": []string{"0"}, diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4fa4cce43..3d538987d 100755 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -24,6 +24,24 @@ import ( log "github.com/golang/glog" ) +//remove redundant elements +func RvRepElement(arr []string) []string { + result := []string{} + for i := 0; i < len(arr); i++ { + flag := true + for j := range result { + if arr[i] == result[j] { + flag = false + break + } + } + if flag == true { + result = append(result, arr[i]) + } + } + return result +} + func Contained(obj, target interface{}) bool { targetValue := reflect.ValueOf(target) switch reflect.TypeOf(target).Kind() { diff --git a/testutils/db/fake.go b/testutils/db/fake.go index 26c07bfcf..54cc64f47 100755 --- a/testutils/db/fake.go +++ b/testutils/db/fake.go @@ -78,6 +78,16 @@ func (fc *FakeDbClient) ListDocks(ctx *c.Context) ([]*model.DockSpec, error) { return dcks, nil } +//ListAvailabilityZones +func (fc *FakeDbClient) ListAvailabilityZones(ctx *c.Context) ([]string, error) { + var azs []string + for i := range SamplePools { + az := SamplePools[i].AvailabilityZone + azs = append(azs, az) + } + return azs, nil +} + // UpdateDock func (fc *FakeDbClient) UpdateDock(ctx *c.Context, dckID, name, desp string) (*model.DockSpec, error) { return nil, nil diff --git a/testutils/db/testing/mock_db.go b/testutils/db/testing/mock_db.go index 71fcdb917..28bff77ed 100755 --- a/testutils/db/testing/mock_db.go +++ b/testutils/db/testing/mock_db.go @@ -675,6 +675,31 @@ func (_m *MockClient) ListExtraProperties(ctx *context.Context, prfID string) (* return r0, r1 } +//ListAvaliableZones +func (_m *MockClient) ListAvailabilityZones(ctx *context.Context) ([]string, error) { + ret := _m.Called(ctx) + var r0 []*model.StoragePoolSpec + var azs []string + if rf, ok := ret.Get(0).(func(*context.Context) []*model.StoragePoolSpec); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.StoragePoolSpec) + } + } + for i := 0; i < len(r0); i++ { + azs = append(azs, r0[i].AvailabilityZone) + } + + var r1 error + if rf, ok := ret.Get(1).(func(*context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + return azs, r1 +} + // ListPools provides a mock function with given fields: ctx func (_m *MockClient) ListPools(ctx *context.Context) ([]*model.StoragePoolSpec, error) { ret := _m.Called(ctx) From 71dd6ee2ef76b75cf130665f783074ad04ba22d6 Mon Sep 17 00:00:00 2001 From: lijuncloud Date: Mon, 6 Aug 2018 20:51:35 +0800 Subject: [PATCH 2/4] add imports strings --- pkg/api/pool_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/api/pool_test.go b/pkg/api/pool_test.go index f66e2824c..971ed3586 100755 --- a/pkg/api/pool_test.go +++ b/pkg/api/pool_test.go @@ -21,6 +21,7 @@ import ( "net/http/httptest" "reflect" "testing" + "strings" "github.com/astaxie/beego" c "github.com/opensds/opensds/pkg/context" From 4201a482b066ef8e5244e972229733afeab73c57 Mon Sep 17 00:00:00 2001 From: lijuncloud Date: Mon, 6 Aug 2018 23:12:47 +0800 Subject: [PATCH 3/4] add router --- pkg/api/pool_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/api/pool_test.go b/pkg/api/pool_test.go index 971ed3586..ffb1fe4f9 100755 --- a/pkg/api/pool_test.go +++ b/pkg/api/pool_test.go @@ -20,8 +20,8 @@ import ( "net/http" "net/http/httptest" "reflect" - "testing" "strings" + "testing" "github.com/astaxie/beego" c "github.com/opensds/opensds/pkg/context" @@ -33,6 +33,7 @@ import ( func init() { var poolPortal PoolPortal beego.Router("/v1beta/pools", &poolPortal, "get:ListPools") + beego.Router("/v1beta/availabilityZones", &poolPortal, "get:ListAvailabilityZones") beego.Router("/v1beta/pools/:poolId", &poolPortal, "get:GetPool") } From 3ee39d84ad3d128eda561ee55bd4a46f58a1fd68 Mon Sep 17 00:00:00 2001 From: lijuncloud Date: Tue, 7 Aug 2018 10:32:07 +0800 Subject: [PATCH 4/4] Add a test case for new function --- pkg/utils/utils_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 8fbe1afcf..bafebf175 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -22,6 +22,16 @@ import ( "github.com/opensds/opensds/pkg/model" ) +func TestRvRepElement(t *testing.T) { + var strs = []string{"default", "default"} + str := RvRepElement(strs) + res := str[0] + var expect = "default" + if len(str) != 1 || res != expect { + t.Errorf("%v remove redundant elements fail,expect:%v,result:%v\n", str, expect, res) + } +} + func TestContained(t *testing.T) { var targets = []interface{}{ []interface{}{"key01", 123, true},