diff --git a/acls.go b/acls.go index 6569a89..6dd962e 100644 --- a/acls.go +++ b/acls.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/acls_test.go b/acls_test.go index 4f7ee4c..1a54d86 100755 --- a/acls_test.go +++ b/acls_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/api/api_ordered_values.go b/api/api_ordered_values.go index 3a38f6d..111e89d 100644 --- a/api/api_ordered_values.go +++ b/api/api_ordered_values.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 api import ( diff --git a/api/api_ordered_values_test.go b/api/api_ordered_values_test.go index 44141a0..456d48d 100644 --- a/api/api_ordered_values_test.go +++ b/api/api_ordered_values_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 api import ( diff --git a/api/api_test.go b/api/api_test.go index af50cf9..896dde5 100755 --- a/api/api_test.go +++ b/api/api_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 api import ( diff --git a/api/common/utils/poll.go b/api/common/utils/poll.go new file mode 100644 index 0000000..dea8831 --- /dev/null +++ b/api/common/utils/poll.go @@ -0,0 +1,111 @@ +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. + + 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 utils + +// Polling functionality inspired by https://github.com/kubernetes/apimachinery + +import ( + "context" + "errors" + "time" +) + +var ErrWaitTimeout = errors.New("timed out waiting for the condition") + +type WaitWithContextFunc func(ctx context.Context) <-chan struct{} +type ConditionWithContextFunc func(context.Context) (done bool, err error) + +func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, true, poller(interval, timeout), condition) +} + +func WaitForWithContext(ctx context.Context, wait WaitWithContextFunc, fn ConditionWithContextFunc) error { + waitCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + c := wait(waitCtx) + for { + select { + case _, open := <-c: + ok, err := fn(ctx) + if err != nil { + return err + } + if ok { + return nil + } + if !open { + return ErrWaitTimeout + } + case <-ctx.Done(): + return ErrWaitTimeout + } + } +} + +func poll(ctx context.Context, immediate bool, wait WaitWithContextFunc, condition ConditionWithContextFunc) error { + if immediate { + done, err := condition(ctx) + if err != nil { + return err + } + if done { + return nil + } + } + + select { + case <-ctx.Done(): + return ErrWaitTimeout + default: + return WaitForWithContext(ctx, wait, condition) + } +} + +func poller(interval, timeout time.Duration) WaitWithContextFunc { + return func(ctx context.Context) <-chan struct{} { + ch := make(chan struct{}) + + go func() { + defer close(ch) + + tick := time.NewTicker(interval) + defer tick.Stop() + + var after <-chan time.Time + if timeout != 0 { + timer := time.NewTimer(timeout) + after = timer.C + defer timer.Stop() + } + + for { + select { + case <-tick.C: + select { + case ch <- struct{}{}: + default: + } + case <-after: + return + case <-ctx.Done(): + return + } + } + }() + + return ch + } +} diff --git a/api/common/utils/utils.go b/api/common/utils/utils.go index b650406..d0828e6 100644 --- a/api/common/utils/utils.go +++ b/api/common/utils/utils.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 utils // IsStringInSlice checks if a string is an element of a string slice diff --git a/api/common/utils/utils_test.go b/api/common/utils/utils_test.go index ec618e4..ae48b5b 100644 --- a/api/common/utils/utils_test.go +++ b/api/common/utils/utils_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 utils import ( diff --git a/api/v1/api_v1.go b/api/v1/api_v1.go index bde9c14..47bd04c 100644 --- a/api/v1/api_v1.go +++ b/api/v1/api_v1.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v1 import ( diff --git a/api/v1/api_v1_exports.go b/api/v1/api_v1_exports.go index 9238102..b43fb63 100644 --- a/api/v1/api_v1_exports.go +++ b/api/v1/api_v1_exports.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v1 import ( diff --git a/api/v1/api_v1_quotas.go b/api/v1/api_v1_quotas.go index 0dd0281..897d422 100644 --- a/api/v1/api_v1_quotas.go +++ b/api/v1/api_v1_quotas.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v1 import ( diff --git a/api/v1/api_v1_snapshots.go b/api/v1/api_v1_snapshots.go index cbb6858..6ac6b8d 100644 --- a/api/v1/api_v1_snapshots.go +++ b/api/v1/api_v1_snapshots.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v1 import ( diff --git a/api/v1/api_v1_zones.go b/api/v1/api_v1_zones.go index 8e69176..b3b19fe 100644 --- a/api/v1/api_v1_zones.go +++ b/api/v1/api_v1_zones.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v1 import ( diff --git a/api/v11/api_v11_replication.go b/api/v11/api_v11_replication.go index 6b88115..0d698c5 100644 --- a/api/v11/api_v11_replication.go +++ b/api/v11/api_v11_replication.go @@ -1,36 +1,145 @@ +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. + + 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 v11 import ( "context" "fmt" "github.com/dell/goisilon/api" + "strconv" +) + +const ( + policiesPath = "/platform/11/sync/policies/" + targetPoliciesPath = "/platform/11/sync/target/policies/" + jobsPath = "/platform/11/sync/jobs/" + reportsPath = "/platform/11/sync/reports" +) + +type JOB_ACTION string + +const ( + RESYNC_PREP JOB_ACTION = "resync_prep" + ALLOW_WRITE JOB_ACTION = "allow_write" + ALLOW_WRITE_REVERT JOB_ACTION = "allow_write_revert" + TEST JOB_ACTION = "test" ) +type RUNNING_JOB_ACTION string + const ( - policiesPath = "/platform/11/sync/policies/" + SYNC RUNNING_JOB_ACTION = "sync" + COPY RUNNING_JOB_ACTION = "copy" ) +type JOB_STATE string + +const ( + SCHEDULED JOB_STATE = "scheduled" + RUNNING JOB_STATE = "running" + PAUSED JOB_STATE = "paused" + FINISHED JOB_STATE = "finished" + FAILED JOB_STATE = "failed" + CANCELED JOB_STATE = "canceled" + NEEDS_ATTENTION JOB_STATE = "needs_attention" + SKIPPED JOB_STATE = "skipped" + PENDING JOB_STATE = "pending" + UNKNOWN JOB_STATE = "unknown" +) + +type FAILOVER_FAILBACK_STATE string + +const ( + WRITES_DISABLED FAILOVER_FAILBACK_STATE = "writes_disabled" + ENABLING_WRITES FAILOVER_FAILBACK_STATE = "enabling_writes" + WRITES_ENABLED FAILOVER_FAILBACK_STATE = "writes_enabled" + DISABLING_WRITES FAILOVER_FAILBACK_STATE = "disabling_writes" + CREATING_RESYNC_POLICY FAILOVER_FAILBACK_STATE = "creating_resync_policy" + RESYNC_POLICY_CREATED FAILOVER_FAILBACK_STATE = "resync_policy_created" +) + +var policyNameArg = []byte("policy_name") +var sortArg = []byte("sort") +var reportsPerPolicyArg = []byte("reports_per_policy") + // Policy contains the CloudIQ policy info. type Policy struct { - Action string `json:"action,omitempty"` - Id string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Enabled bool `json:"enabled,omitempty"` - TargetPath string `json:"target_path,omitempty"` - SourcePath string `json:"source_root_path,omitempty"` - TargetHost string `json:"target_host,omitempty"` - JobDelay int `json:"job_delay,omitempty"` - Schedule string `json:"schedule,omitempty"` + Action string `json:"action,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Enabled bool `json:"enabled"` + TargetPath string `json:"target_path,omitempty"` + SourcePath string `json:"source_root_path,omitempty"` + TargetHost string `json:"target_host,omitempty"` + TargetCert string `json:"target_certificate_id,omitempty"` + JobDelay int `json:"job_delay,omitempty"` + Schedule string `json:"schedule,omitempty"` + LastJobState JOB_STATE `json:"last_job_state,omitempty"` } type Policies struct { Policy []Policy `json:"policies,omitempty"` } +type Reports struct { + Reports []Report `json:"reports,omitempty"` +} + +type TargetPolicy struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + SourceClusterGuid string `json:"source_cluster_guid,omitempty"` + LastJobState JOB_STATE `json:"last_job_state,omitempty"` + TargetPath string `json:"target_path,omitempty"` + SourceHost string `json:"source_host,omitempty"` + LastSourceCoordinatorIp string `json:"last_source_coordinator_ip,omitempty"` + FailoverFailbackState FAILOVER_FAILBACK_STATE `json:"failover_failback_state,omitempty"` +} + +type TargetPolicies struct { + Policy []TargetPolicy `json:"policies,omitempty"` +} + +type JobRequest struct { + Action JOB_ACTION `json:"action,omitempty"` + Id string `json:"id,omitempty"` // ID or Name of policy + SkipFailover bool `json:"skip_failover,omitempty"` + SkipMap bool `json:"skip_map,omitempty"` + SkipCopy bool `json:"skip_copy,omitempty"` +} + +type Job struct { + Action RUNNING_JOB_ACTION `json:"policy_action,omitempty"` + Id string `json:"id,omitempty"` // ID or Name of policy +} + +type Jobs struct { + Job []Job `json:"jobs,omitempty"` +} + +type Report struct { + Policy Policy `json:"policy,omitempty"` + Id string `json:"id"` + JobId int64 `json:"job_id"` + State JOB_STATE `json:"state,omitempty"` + EndTime int64 `json:"end_time"` +} + // GetPolicyByName returns policy by name -func GetPolicyByName( - ctx context.Context, - client api.Client, name string) (policy *Policy, err error) { +func GetPolicyByName(ctx context.Context, client api.Client, name string) (policy *Policy, err error) { p := &Policies{} err = client.Get(ctx, policiesPath, name, nil, nil, &p) if err != nil { @@ -41,23 +150,101 @@ func GetPolicyByName( return &p.Policy[0], nil } -func CreatePolicy(ctx context.Context, client api.Client, name string, sourcePath string, targetPath string, targetHost string, rpo int) error { - resp := "" +func GetTargetPolicyByName(ctx context.Context, client api.Client, name string) (policy *TargetPolicy, err error) { + p := &TargetPolicies{} + err = client.Get(ctx, targetPoliciesPath, name, nil, nil, &p) + if err != nil || len(p.Policy) == 0 { + return nil, err + } + return &p.Policy[0], nil +} + +func CreatePolicy(ctx context.Context, client api.Client, name string, sourcePath string, targetPath string, targetHost string, targetCert string, rpo int, enabled bool) error { + var policyResp Policy body := &Policy{ Action: "sync", Id: "", Name: name, - Enabled: true, + Enabled: enabled, TargetPath: targetPath, SourcePath: sourcePath, TargetHost: targetHost, JobDelay: rpo, + TargetCert: targetCert, Schedule: "when-source-modified", } - return client.Post(ctx, policiesPath, "", nil, nil, body, resp) + return client.Post(ctx, policiesPath, "", nil, nil, body, &policyResp) } func DeletePolicy(ctx context.Context, client api.Client, name string) error { resp := "" return client.Delete(ctx, policiesPath, name, nil, nil, &resp) -} \ No newline at end of file +} + +func DeleteTargetPolicy(ctx context.Context, client api.Client, id string) error { + resp := "" + return client.Delete(ctx, targetPoliciesPath, id, nil, nil, &resp) +} + +func UpdatePolicy(ctx context.Context, client api.Client, policy *Policy) error { + id := policy.Id + policy.Id = "" + + return client.Put(ctx, policiesPath, id, nil, nil, policy, nil) +} + +func ResetPolicy(ctx context.Context, client api.Client, name string) error { + resp := Policy{} + return client.Post(ctx, policiesPath, name+"/reset", nil, nil, nil, &resp) +} + +func StartSyncIQJob(ctx context.Context, client api.Client, job *JobRequest) (*Job, error) { + var jobResp Job + return &jobResp, client.Post(ctx, jobsPath, "", nil, nil, job, &jobResp) +} + +func GetReport(ctx context.Context, client api.Client, reportName string) (*Report, error) { + r := &Reports{} + err := client.Get(ctx, reportsPath, reportName, nil, nil, &r) + if err != nil { + return nil, err + } + if len(r.Reports) == 0 { + return nil, fmt.Errorf("no reports found with report name %s", reportName) + } + return &r.Reports[0], nil +} + +func GetReportsByPolicyName(ctx context.Context, client api.Client, policyName string, reportsForPolicy int) (*Reports, error) { + r := &Reports{} + err := client.Get(ctx, reportsPath, "", + api.OrderedValues{ + {policyNameArg, []byte(policyName)}, + {sortArg, []byte("end_time")}, + {reportsPerPolicyArg, []byte(strconv.Itoa(reportsForPolicy))}, + }, + nil, &r) + if err != nil { + return nil, err + } + + if len(r.Reports) == 0 { + return nil, fmt.Errorf("no reports found for policy %s", policyName) + } + + return r, nil +} + +func GetJobsByPolicyName(ctx context.Context, client api.Client, policyName string) ([]Job, error) { + j := &Jobs{} + err := client.Get(ctx, jobsPath, policyName, nil, nil, &j) + if err != nil { + if e,ok:= err.(*api.JSONError);ok{ + if e.StatusCode == 404{ + return []Job{}, nil + } + } + return nil, err + } + return j.Job, nil +} diff --git a/api/v2/api_v2.go b/api/v2/api_v2.go index 08a34e0..7041c94 100644 --- a/api/v2/api_v2.go +++ b/api/v2/api_v2.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v2/api_v2_acls.go b/api/v2/api_v2_acls.go index a03ff1e..2f3d925 100644 --- a/api/v2/api_v2_acls.go +++ b/api/v2/api_v2_acls.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v2/api_v2_exports.go b/api/v2/api_v2_exports.go index 602f961..ebe7ba1 100644 --- a/api/v2/api_v2_exports.go +++ b/api/v2/api_v2_exports.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v2/api_v2_exports_json_test.go b/api/v2/api_v2_exports_json_test.go index a10ce98..a485717 100644 --- a/api/v2/api_v2_exports_json_test.go +++ b/api/v2/api_v2_exports_json_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 var getOneExportJSON = []byte(`{ "exports" : [ diff --git a/api/v2/api_v2_exports_test.go b/api/v2/api_v2_exports_test.go index 6621ed9..a3239ff 100644 --- a/api/v2/api_v2_exports_test.go +++ b/api/v2/api_v2_exports_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v2/api_v2_fs.go b/api/v2/api_v2_fs.go index ee7ce9b..05f6364 100644 --- a/api/v2/api_v2_fs.go +++ b/api/v2/api_v2_fs.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v2/api_v2_types.go b/api/v2/api_v2_types.go index a30b42c..3da420a 100644 --- a/api/v2/api_v2_types.go +++ b/api/v2/api_v2_types.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v2 import ( diff --git a/api/v3/api_v3.go b/api/v3/api_v3.go index a738a7b..40f8548 100644 --- a/api/v3/api_v3.go +++ b/api/v3/api_v3.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v3 import ( diff --git a/api/v3/api_v3_cluster.go b/api/v3/api_v3_cluster.go index e48da14..8cc3b7a 100644 --- a/api/v3/api_v3_cluster.go +++ b/api/v3/api_v3_cluster.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v3 import ( diff --git a/api/v3/api_v3_types.go b/api/v3/api_v3_types.go index 0ced3e1..08fb266 100644 --- a/api/v3/api_v3_types.go +++ b/api/v3/api_v3_types.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 v3 type isiStats struct { diff --git a/client_test.go b/client_test.go index 75c4a0c..6bc93d9 100644 --- a/client_test.go +++ b/client_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/cluster.go b/cluster.go index 1235229..6129f46 100644 --- a/cluster.go +++ b/cluster.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/cluster_test.go b/cluster_test.go index 81be1ae..70d2a61 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import "testing" diff --git a/exports_test.go b/exports_test.go index c9666c4..4e9a192 100644 --- a/exports_test.go +++ b/exports_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( @@ -315,20 +315,20 @@ func TestExportClientsAdd(t *testing.T) { } func TestAddExportClientsByID(t *testing.T) { - - // Add the test exports - volumeName1 := "test_get_exports1" - vol, err := client.CreateVolume(defaultCtx, volumeName1) - assertNoError(t, err) - assertNotNil(t, vol) - volumeName1 = vol.Name - t.Logf("created volume: %s", volumeName1) - - exportID, err := client.Export(defaultCtx, volumeName1) - assertNoError(t, err) - t.Logf("created export: %d", exportID) - - exportForClient = exportID + + // Add the test exports + volumeName1 := "test_get_exports1" + vol, err := client.CreateVolume(defaultCtx, volumeName1) + assertNoError(t, err) + assertNotNil(t, vol) + volumeName1 = vol.Name + t.Logf("created volume: %s", volumeName1) + + exportID, err := client.Export(defaultCtx, volumeName1) + assertNoError(t, err) + t.Logf("created export: %d", exportID) + + exportForClient = exportID export, _ := client.GetExportByID(defaultCtx, exportID) fmt.Printf("export '%d' has \n%-20v: '%v'\n%-20v: '%v'\n%-20v: '%v'\n", exportID, "clients", *export.Clients, "read_only_cilents", *export.ReadOnlyClients, "read_write_cilents", *export.ReadWriteClients) @@ -365,19 +365,19 @@ func TestRemoveExportClientsByID(t *testing.T) { func TestRemoveExportClientsByName(t *testing.T) { testRemoveExportClients(t, nil, client.RemoveExportClientsByName) - volumeName1 := "test_get_exports1" - // make sure we clean up when we're done - defer client.Unexport(defaultCtx, volumeName1) - defer client.DeleteVolume(defaultCtx, volumeName1) + volumeName1 := "test_get_exports1" + // make sure we clean up when we're done + defer client.Unexport(defaultCtx, volumeName1) + defer client.DeleteVolume(defaultCtx, volumeName1) } func testRemoveExportClients(t *testing.T, removeExportClientsByIDFunc func(ctx context.Context, id int, clientsToRemove []string) error, removeExportClientsByNameFunc func(ctx context.Context, name string, clientsToRemove []string) error) { - volumeName1 := "test_get_exports1" + volumeName1 := "test_get_exports1" - exportID := exportForClient + exportID := exportForClient export, _ := client.GetExportByName(defaultCtx, volumeName1) exportName := volumeName1 diff --git a/quota.go b/quota.go index 7ebf0e7..eb4f17a 100644 --- a/quota.go +++ b/quota.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/quota_test.go b/quota_test.go index c9cd004..498fa03 100644 --- a/quota_test.go +++ b/quota_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/replication.go b/replication.go index 3d836ff..c77db2f 100644 --- a/replication.go +++ b/replication.go @@ -2,21 +2,344 @@ package goisilon import ( "context" + log "github.com/akutz/gournal" + "github.com/dell/goisilon/api/common/utils" apiv11 "github.com/dell/goisilon/api/v11" + "time" +) + +const defaultPoll = 5 * time.Second +const defaultTimeout = 10 * time.Minute // set high timeout, we expect to be canceled via context before + +const ( + RESYNC_PREP apiv11.JOB_ACTION = "resync_prep" + ALLOW_WRITE apiv11.JOB_ACTION = "allow_write" + ALLOW_WRITE_REVERT apiv11.JOB_ACTION = "allow_write_revert" + TEST apiv11.JOB_ACTION = "test" + SCHEDULED apiv11.JOB_STATE = "scheduled" + RUNNING apiv11.JOB_STATE = "running" + PAUSED apiv11.JOB_STATE = "paused" + FINISHED apiv11.JOB_STATE = "finished" + FAILED apiv11.JOB_STATE = "failed" + CANCELED apiv11.JOB_STATE = "canceled" + NEEDS_ATTENTION apiv11.JOB_STATE = "needs_attention" + SKIPPED apiv11.JOB_STATE = "skipped" + PENDING apiv11.JOB_STATE = "pending" + UNKNOWN apiv11.JOB_STATE = "unknown" + WRITES_DISABLED apiv11.FAILOVER_FAILBACK_STATE = "writes_disabled" + ENABLING_WRITES apiv11.FAILOVER_FAILBACK_STATE = "enabling_writes" + WRITES_ENABLED apiv11.FAILOVER_FAILBACK_STATE = "writes_enabled" + DISABLING_WRITES apiv11.FAILOVER_FAILBACK_STATE = "disabling_writes" + CREATING_RESYNC_POLICY apiv11.FAILOVER_FAILBACK_STATE = "creating_resync_policy" + RESYNC_POLICY_CREATED apiv11.FAILOVER_FAILBACK_STATE = "resync_policy_created" ) // Policy is an Isilon Policy type Policy *apiv11.Policy +type TargetPolicy *apiv11.TargetPolicy + // GetPolicyByName returns a policy with the provided ID. func (c *Client) GetPolicyByName(ctx context.Context, id string) (Policy, error) { - return apiv11.GetPolicyByName(ctx,c.API,id) + return apiv11.GetPolicyByName(ctx, c.API, id) +} + +func (c *Client) GetTargetPolicyByName(ctx context.Context, id string) (TargetPolicy, error) { + return apiv11.GetTargetPolicyByName(ctx, c.API, id) +} + +func (c *Client) CreatePolicy(ctx context.Context, name string, rpo int, sourcePath string, targetPath string, targetHost string, targetCert string, enabled bool) error { + return apiv11.CreatePolicy(ctx, c.API, name, sourcePath, targetPath, targetHost, targetCert, rpo, enabled) +} + +func (c *Client) DeletePolicy(ctx context.Context, name string) error { + return apiv11.DeletePolicy(ctx, c.API, name) +} + +func (c *Client) DeleteTargetPolicy(ctx context.Context, id string) error { + return apiv11.DeleteTargetPolicy(ctx, c.API, id) +} + +func (c *Client) BreakAssociation(ctx context.Context, targetPolicyName string) error { + tp, err := apiv11.GetTargetPolicyByName(ctx, c.API, targetPolicyName) + if err != nil { + return err + } + + return c.DeleteTargetPolicy(ctx, tp.Id) +} + +func (c *Client) ResetPolicy(ctx context.Context, name string) error { + return apiv11.ResetPolicy(ctx, c.API, name) +} + +func (c *Client) EnablePolicy(ctx context.Context, name string) error { + return c.SetPolicyEnabledField(ctx, name, true) +} + +func (c *Client) DisablePolicy(ctx context.Context, name string) error { + return c.SetPolicyEnabledField(ctx, name, false) +} + +func (c *Client) SetPolicyEnabledField(ctx context.Context, name string, value bool) error { + pp, err := c.GetPolicyByName(ctx, name) + if err != nil { + return err + } + if pp == nil { + return nil + } + + if pp.Enabled == value { + return nil + } + + p := &apiv11.Policy{ + Id: pp.Id, + Enabled: value, + } + + return apiv11.UpdatePolicy(ctx, c.API, p) +} + +func (c *Client) AllowWrites(ctx context.Context, policyName string) error { + targetPolicy, err := c.GetTargetPolicyByName(ctx, policyName) + if err != nil { + return err + } + if targetPolicy.FailoverFailbackState == WRITES_ENABLED { + return nil + } + + _, err = c.RunActionForPolicy(ctx, policyName, apiv11.ALLOW_WRITE) + if err != nil { + return err + } + + err = c.WaitForTargetPolicyCondition(ctx, policyName, WRITES_ENABLED) + if err != nil { + return err + } + + return nil +} + +func (c *Client) DisallowWrites(ctx context.Context, policyName string) error { + targetPolicy, err := c.GetTargetPolicyByName(ctx, policyName) + if err != nil { + return err + } + if targetPolicy.FailoverFailbackState == WRITES_DISABLED { + return nil + } + + _, err = c.RunActionForPolicy(ctx, policyName, apiv11.ALLOW_WRITE_REVERT) + if err != nil { + return err + } + + err = c.WaitForTargetPolicyCondition(ctx, policyName, WRITES_DISABLED) + if err != nil { + return err + } + + return nil +} + +func (c *Client) ResyncPrep(ctx context.Context, policyName string) error { + targetPolicy, err := c.GetTargetPolicyByName(ctx, policyName) + if err != nil { + return err + } + if targetPolicy.FailoverFailbackState == RESYNC_POLICY_CREATED { + return nil + } + + _, err = c.RunActionForPolicy(ctx, policyName, apiv11.RESYNC_PREP) + if err != nil { + return err + } + + err = c.WaitForTargetPolicyCondition(ctx, policyName, RESYNC_POLICY_CREATED) + if err != nil { + return err + } + + return nil } -func (c *Client) CreatePolicy(ctx context.Context, name string, rpo int, sourcePath string, targetPath string, targetHost string) error{ - return apiv11.CreatePolicy(ctx,c.API,name,sourcePath,targetPath,targetHost,rpo) +func (c *Client) RunActionForPolicy(ctx context.Context, policyName string, action apiv11.JOB_ACTION) (*apiv11.Job, error) { + job := &apiv11.JobRequest{ + Id: policyName, + Action: action, + } + + return apiv11.StartSyncIQJob(ctx, c.API, job) +} + +func (c *Client) StartSyncIQJob(ctx context.Context, job *apiv11.JobRequest) (*apiv11.Job, error) { + return apiv11.StartSyncIQJob(ctx, c.API, job) } -func (c *Client) DeletePolicy(ctx context.Context, name string) error{ - return apiv11.DeletePolicy(ctx,c.API,name) -} \ No newline at end of file +func (c *Client) GetReport(ctx context.Context, reportName string) (*apiv11.Report, error) { + return apiv11.GetReport(ctx, c.API, reportName) +} + +func (c *Client) GetReportsByPolicyName(ctx context.Context, policyName string, reportsForPolicy int) (*apiv11.Reports, error) { + return apiv11.GetReportsByPolicyName(ctx, c.API, policyName, reportsForPolicy) +} + +func (c *Client) WaitForPolicyEnabledFieldCondition(ctx context.Context, policyName string, enabled bool) error { + pollErr := utils.PollImmediateWithContext(ctx, defaultPoll, defaultTimeout, + func(iCtx context.Context) (bool, error) { + p, err := c.GetPolicyByName(iCtx, policyName) + if err != nil { + return false, err + } + + if p.Enabled != enabled { + return false, nil + } + + return true, nil + }) + + if pollErr != nil { + return pollErr + } + + return nil +} + +func (c *Client) WaitForNoActiveJobs(ctx context.Context, policyName string) error { + pollErr := utils.PollImmediateWithContext(ctx, defaultPoll, defaultTimeout, + func(iCtx context.Context) (bool, error) { + p, err := c.GetJobsByPolicyName(iCtx, policyName) + if err != nil { + return false, err + } + + if len(p)!= 0 { + return false, nil + } + + return true, nil + }) + + if pollErr != nil { + return pollErr + } + + return nil +} + +func (c *Client) WaitForPolicyLastJobState(ctx context.Context, policyName string, state apiv11.JOB_STATE) error { + pollErr := utils.PollImmediateWithContext(ctx, defaultPoll, defaultTimeout, + func(iCtx context.Context) (bool, error) { + p, err := c.GetPolicyByName(iCtx, policyName) + if err != nil { + return false, err + } + + if p.LastJobState != state { + return false, nil + } + + return true, nil + }) + + if pollErr != nil { + return pollErr + } + + return nil +} + +func (c *Client) WaitForTargetPolicyCondition(ctx context.Context, policyName string, condition apiv11.FAILOVER_FAILBACK_STATE) error { + pollErr := utils.PollImmediateWithContext(ctx, defaultPoll, defaultTimeout, + func(iCtx context.Context) (bool, error) { + tp, err := c.GetTargetPolicyByName(iCtx, policyName) + if err != nil { + return false, err + } + + if tp.FailoverFailbackState != condition { + return false, nil + } + + return true, nil + }) + + if pollErr != nil { + return pollErr + } + + return nil +} + +func (c *Client) SyncPolicy(ctx context.Context, policyName string) error { + + // get all running + // if running - wait for it and succeed + // if no running - start new - wait for it and succeed + + var isRunning bool + + policy, err := c.GetPolicyByName(ctx,policyName) + if err != nil { + return err + } + if policy.Enabled != true{ + return nil + } + + runningJobs, err := c.GetJobsByPolicyName(ctx, policyName) + if err != nil { + log.Info(ctx,err.Error()) + return err + } + for _, i := range runningJobs { + if i.Action == apiv11.SYNC { + // running job detected. Wait for it to complete. + isRunning = true + } + } + if isRunning { + log.Info(ctx, "found active jobs, waiting for completion") + err = c.WaitForNoActiveJobs(ctx, policyName) + if err != nil { + return err + } + return nil + } else { + jobReq := &apiv11.JobRequest{ + Id: policyName, + } + log.Info(ctx, "found no active sync jobs, starting a new one") + _, err := c.StartSyncIQJob(ctx, jobReq) + if err != nil { + return err + } + time.Sleep(3 * time.Second) + err = c.WaitForNoActiveJobs(ctx, policyName) + if err != nil { + return err + } + return nil + } + +} + +func (c *Client) GetJobsByPolicyName(ctx context.Context, policyName string) ([]apiv11.Job, error) { + return apiv11.GetJobsByPolicyName(ctx, c.API, policyName) +} + +func FilterReports(values []apiv11.Report, filterFunc func(apiv11.Report) bool) []apiv11.Report { + filtered := make([]apiv11.Report, 0) + for _, v := range values { + if filterFunc(v) { + filtered = append(filtered, v) + } + } + return filtered +} diff --git a/replication_test.go b/replication_test.go new file mode 100644 index 0000000..d647a6d --- /dev/null +++ b/replication_test.go @@ -0,0 +1,280 @@ +package goisilon_test + +import ( + "context" + "fmt" + "github.com/dell/goisilon" + "github.com/dell/goisilon/api" + "github.com/stretchr/testify/suite" + "testing" + "time" +) + +type ReplicationTestSuite struct { + suite.Suite + localClient *goisilon.Client + remoteClient *goisilon.Client + localEndpoint string + remoteEndpoint string +} + +func (suite *ReplicationTestSuite) SetupSuite() { + lc, err := goisilon.NewClientWithArgs( + context.Background(), + "https://10.225.111.21:8080", + true, + 1, + "admin", + "", + "dangerous", + "/ifs/data/test-goisilon", + "0777", 0) + if err != nil { + panic(err) + } + suite.localClient = lc + suite.localEndpoint = "10.225.111.21" + + rc, err := goisilon.NewClientWithArgs( + context.Background(), + "https://10.225.111.70:8080", + true, + 1, + "admin", + "", + "dangerous", + "/ifs/data/test-goisilon", + "0777", 0) + if err != nil { + panic(err) + } + suite.remoteClient = rc + suite.remoteEndpoint = "10.225.111.70" +} + +func (suite *ReplicationTestSuite) TearDownSuite() { +} + +func (suite *ReplicationTestSuite) TestUnplannedFailoverScenario() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + volumeName := "replicated" + + // *** SIMULATE CREATE_VOLUME CALL *** // + + // Create volume that would serve as VG + volume, err := suite.localClient.CreateVolume(ctx, volumeName) + suite.NoError(err) + suite.NotNil(volume) + + // defer func() { + // err := suite.localClient.DeleteVolume(ctx, volumeName) + // suite.NoError(err) + // }() + + res, err := suite.localClient.GetVolume(context.Background(), "", volumeName) + suite.NoError(err) + fmt.Println("local", res) + + err = suite.localClient.CreatePolicy(ctx, + volumeName, + 300, + "/ifs/data/test-goisilon/replicated", + "/ifs/data/test-goisilon/replicated", + suite.remoteEndpoint, + true) + // suite.NoError(err) + + p, err := suite.localClient.GetPolicyByName(ctx, volumeName) + suite.NoError(err) + suite.NotNil(p) + fmt.Println("local policy", p) + + err = suite.localClient.WaitForPolicyLastJobState(ctx, volumeName, goisilon.FINISHED) + suite.NoError(err) + + // defer func() { + // err := suite.localClient.DeletePolicy(ctx, volumeName) + // suite.NoError(err) + // }() + + // *** SIMULATE EXECUTE_ACTION UNPLANNED_FAILOVER CALL *** + + err = suite.remoteClient.BreakAssociation(ctx, volumeName) + suite.NoError(err) + + + // *** SIMULATE EXECUTE_ACTION REPROTECT CALL *** + // In driver EXECUTE_ACTION reprotect will be called on another side, but here we just talk to remote client + + local := suite.remoteClient + remote := suite.localClient + + pp, err := remote.GetPolicyByName(ctx, volumeName) + suite.NoError(err) + + if pp.Enabled { + // Disable policy on remote + err = remote.DisablePolicy(ctx, volumeName) + suite.NoError(err) + + err = remote.WaitForPolicyEnabledFieldCondition(ctx, volumeName, false) + suite.NoError(err) + + // Run reset on the policy + err = remote.ResetPolicy(ctx, volumeName) + suite.NoError(err) + + + // Create policy on local (actually get it before creating it) + err = local.CreatePolicy(ctx, + volumeName, + 300, + "/ifs/data/test-goisilon/replicated", + "/ifs/data/test-goisilon/replicated", + suite.localEndpoint, + true) + suite.NoError(err) + + err = local.WaitForPolicyEnabledFieldCondition(ctx, volumeName, true) + suite.NoError(err) + + + } else { + err = local.EnablePolicy(ctx, volumeName) + suite.NoError(err) + + err = local.WaitForPolicyEnabledFieldCondition(ctx, volumeName, true) + suite.NoError(err) + } + + + tp, err := remote.GetTargetPolicyByName(ctx, volumeName) + if err != nil { + if e, ok := err.(*api.JSONError); ok { + if e.StatusCode != 404 { + suite.NoError(err) + } + } + } + + if tp != nil { + err = remote.DisallowWrites(ctx, volumeName) + suite.NoError(err) + } +} + +func (suite *ReplicationTestSuite) TestReplication() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + volumeName := "replicated" + + // *** SIMULATE CREATE_VOLUME CALL *** // + + // Create volume that would serve as VG + volume, err := suite.localClient.CreateVolume(ctx, volumeName) + suite.NoError(err) + suite.NotNil(volume) + + // defer func() { + // err := suite.localClient.DeleteVolume(ctx, volumeName) + // suite.NoError(err) + // }() + + res, err := suite.localClient.GetVolume(context.Background(), "", volumeName) + suite.NoError(err) + fmt.Println("local", res) + + err = suite.localClient.CreatePolicy(ctx, + volumeName, + 300, + "/ifs/data/test-goisilon/replicated", + "/ifs/data/test-goisilon/replicated", + suite.remoteEndpoint, + true) + suite.NoError(err) + + p, err := suite.localClient.GetPolicyByName(ctx, volumeName) + suite.NoError(err) + suite.NotNil(p) + fmt.Println("local policy", p) + + err = suite.localClient.WaitForPolicyLastJobState(ctx, volumeName, goisilon.FINISHED) + suite.NoError(err) + + // defer func() { + // err := suite.localClient.DeletePolicy(ctx, volumeName) + // suite.NoError(err) + // }() + + // *** SIMULATE EXECUTE_ACTION FAILOVER CALL *** + + err = suite.localClient.SyncPolicy(ctx, volumeName) + suite.NoError(err) + + // Create Remote Policy + err = suite.remoteClient.CreatePolicy(ctx, + volumeName, + 300, + "/ifs/data/test-goisilon/replicated", + "/ifs/data/test-goisilon/replicated", + suite.remoteEndpoint, + false) + suite.NoError(err) + + rp, err := suite.remoteClient.GetPolicyByName(ctx, volumeName) + suite.NoError(err) + suite.NotNil(rp) + suite.Equal(rp.Enabled, false) + fmt.Println("remote policy", rp) + + err = suite.remoteClient.WaitForPolicyLastJobState(ctx, volumeName, goisilon.UNKNOWN) + suite.NoError(err) + + // defer func() { + // err := suite.remoteClient.DeletePolicy(ctx, volumeName) + // suite.NoError(err) + // }() + + // Allow writes on remote + err = suite.remoteClient.AllowWrites(ctx, volumeName) + suite.NoError(err) + + // Disable policy on local + err = suite.localClient.DisablePolicy(ctx, volumeName) + suite.NoError(err) + + err = suite.localClient.WaitForPolicyEnabledFieldCondition(ctx, volumeName, false) + suite.NoError(err) + + // Disable writes on local (if we can) + tp, err := suite.localClient.GetTargetPolicyByName(ctx, volumeName) + if err != nil { + if e, ok := err.(*api.JSONError); ok { + if e.StatusCode != 404 { + suite.NoError(err) + } + } + } + + fmt.Println("local target policy", tp) + + if tp != nil { + err = suite.localClient.DisallowWrites(ctx, volumeName) + suite.NoError(err) + } + + // *** SIMULATE EXECUTE_ACTION REPROTECT CALL *** + // In driver EXECUTE_ACTION reprotect will be called on another side, but here we just talk to remote client + err = suite.remoteClient.EnablePolicy(ctx, volumeName) + suite.NoError(err) + + err = suite.remoteClient.WaitForPolicyEnabledFieldCondition(ctx, volumeName, true) + suite.NoError(err) +} + +func TestReplicationSuite(t *testing.T) { + suite.Run(t, new(ReplicationTestSuite)) +} diff --git a/zones.go b/zones.go index 230d566..ff26e57 100644 --- a/zones.go +++ b/zones.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import ( diff --git a/zones_test.go b/zones_test.go index dc4d92c..63448b9 100644 --- a/zones_test.go +++ b/zones_test.go @@ -1,5 +1,5 @@ -/* - Copyright (c) 2019 Dell Inc, or its subsidiaries. +/* + Copyright (c) 2022 Dell Inc, or its subsidiaries. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ 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 goisilon import "testing"