From 3da8bfb1b9e7310e2c936b66ff5db22785e9dbcb Mon Sep 17 00:00:00 2001 From: ajanikow Date: Tue, 1 Oct 2019 23:29:45 +0000 Subject: [PATCH 1/6] Update states --- .../templates/backup-policy.yaml | 41 ++- chart/kube-arangodb-crd/templates/backup.yaml | 50 ++-- .../templates/backup-operator/role.yaml | 3 + .../Deployment/Kubernetes/BackupResource.md | 18 ++ go.mod | 3 +- go.sum | 4 + pkg/apis/backup/v1alpha/backup_state.go | 36 ++- pkg/apis/backup/v1alpha/backup_status.go | 52 ++++ .../handlers/arango/backup/arango_client.go | 62 +---- .../arango/backup/arango_client_mock_test.go | 159 +++++------ .../arango/backup/arango_client_test.go | 2 +- .../arango/backup/backup_suite_test.go | 31 ++- pkg/backup/handlers/arango/backup/errors.go | 119 +++++++++ .../handlers/arango/backup/finalizer.go | 31 +++ pkg/backup/handlers/arango/backup/handler.go | 68 +++-- pkg/backup/handlers/arango/backup/state.go | 2 +- .../handlers/arango/backup/state_create.go | 41 ++- .../arango/backup/state_create_test.go | 54 ++-- .../handlers/arango/backup/state_deleted.go | 4 +- .../handlers/arango/backup/state_download.go | 42 +-- .../arango/backup/state_download_test.go | 30 ++- .../arango/backup/state_downloaderror.go | 13 +- .../arango/backup/state_downloaderror_test.go | 7 +- .../arango/backup/state_downloading.go | 73 ++--- .../arango/backup/state_downloading_test.go | 137 ++++++---- .../handlers/arango/backup/state_failed.go | 4 +- .../handlers/arango/backup/state_none.go | 7 +- .../handlers/arango/backup/state_none_test.go | 4 +- .../handlers/arango/backup/state_pending.go | 16 +- .../arango/backup/state_pending_test.go | 16 +- .../handlers/arango/backup/state_ready.go | 71 +++-- .../arango/backup/state_ready_test.go | 251 +++++++++++------- .../handlers/arango/backup/state_scheduled.go | 15 +- .../arango/backup/state_scheduled_test.go | 12 +- .../handlers/arango/backup/state_upload.go | 41 +-- .../arango/backup/state_upload_test.go | 205 ++++++++------ .../arango/backup/state_uploaderror.go | 30 +-- .../arango/backup/state_uploaderror_test.go | 7 +- .../handlers/arango/backup/state_uploading.go | 60 ++--- .../arango/backup/state_uploading_test.go | 154 +++++++---- pkg/backup/handlers/arango/backup/status.go | 148 +++++++++++ pkg/backup/handlers/arango/backup/util.go | 51 +--- tests/backup_test.go | 37 ++- 43 files changed, 1358 insertions(+), 853 deletions(-) create mode 100644 pkg/backup/handlers/arango/backup/errors.go create mode 100644 pkg/backup/handlers/arango/backup/status.go diff --git a/chart/kube-arangodb-crd/templates/backup-policy.yaml b/chart/kube-arangodb-crd/templates/backup-policy.yaml index fdeb42f16..cd6cb559a 100644 --- a/chart/kube-arangodb-crd/templates/backup-policy.yaml +++ b/chart/kube-arangodb-crd/templates/backup-policy.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: arangobackups.backup.arangodb.com + name: arangobackuppolicies.backup.arangodb.com labels: app.kubernetes.io/name: {{ template "kube-arangodb-crd.name" . }} helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} @@ -11,39 +11,28 @@ metadata: spec: group: backup.arangodb.com additionalPrinterColumns: - - JSONPath: .spec.policyName - description: Policy name - name: Policy + - JSONPath: .spec.schedule + description: Schedule + name: Schedule type: string - - JSONPath: .spec.deployment.name - description: Deployment name - name: Deployment - type: string - - JSONPath: .status.backup.version - description: Backup Version - name: Version - type: string - - JSONPath: .status.backup.createdAt - description: Backup Creation Timestamp - name: Created - type: string - - JSONPath: .status.state - description: The actual state of the ArangoBackup - name: State + - JSONPath: .status.scheduled + description: Scheduled + name: Scheduled type: string - JSONPath: .status.message priority: 1 - description: Message of the ArangoBackup object + description: Message of the ArangoBackupPolicy object name: Message type: string names: - kind: ArangoBackup - listKind: ArangoBackupList - plural: arangobackups + kind: ArangoBackupPolicy + listKind: ArangoBackupPolicyList + plural: arangobackuppolicies shortNames: - - arangobackup - singular: arangobackup + - arangobackuppolicy + - arangobp + singular: arangobackuppolicy scope: Namespaced version: v1alpha subresources: - status: {} \ No newline at end of file + status: {} diff --git a/chart/kube-arangodb-crd/templates/backup.yaml b/chart/kube-arangodb-crd/templates/backup.yaml index cd6cb559a..2210da01d 100644 --- a/chart/kube-arangodb-crd/templates/backup.yaml +++ b/chart/kube-arangodb-crd/templates/backup.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: arangobackuppolicies.backup.arangodb.com + name: arangobackups.backup.arangodb.com labels: app.kubernetes.io/name: {{ template "kube-arangodb-crd.name" . }} helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} @@ -11,28 +11,48 @@ metadata: spec: group: backup.arangodb.com additionalPrinterColumns: - - JSONPath: .spec.schedule - description: Schedule - name: Schedule + - JSONPath: .spec.policyName + description: Policy name + name: Policy type: string - - JSONPath: .status.scheduled - description: Scheduled - name: Scheduled + - JSONPath: .spec.deployment.name + description: Deployment name + name: Deployment + type: string + - JSONPath: .status.backup.version + description: Backup Version + name: Version + type: string + - JSONPath: .status.backup.createdAt + description: Backup Creation Timestamp + name: Created + type: string + - JSONPath: .status.backup.sizeInBytes + description: Backup Size in Bytes + name: Size + type: integer + format: byte + - JSONPath: .status.backup.numberOfDBServers + description: Backup Number of the DB Servers + name: DBServers + type: integer + - JSONPath: .status.state + description: The actual state of the ArangoBackup + name: State type: string - JSONPath: .status.message priority: 1 - description: Message of the ArangoBackupPolicy object + description: Message of the ArangoBackup object name: Message type: string names: - kind: ArangoBackupPolicy - listKind: ArangoBackupPolicyList - plural: arangobackuppolicies + kind: ArangoBackup + listKind: ArangoBackupList + plural: arangobackups shortNames: - - arangobackuppolicy - - arangobp - singular: arangobackuppolicy + - arangobackup + singular: arangobackup scope: Namespaced version: v1alpha subresources: - status: {} + status: {} \ No newline at end of file diff --git a/chart/kube-arangodb/templates/backup-operator/role.yaml b/chart/kube-arangodb/templates/backup-operator/role.yaml index c99686d44..8d4aff472 100644 --- a/chart/kube-arangodb/templates/backup-operator/role.yaml +++ b/chart/kube-arangodb/templates/backup-operator/role.yaml @@ -16,6 +16,9 @@ rules: - apiGroups: [""] resources: ["pods", "services", "endpoints"] verbs: ["get", "update"] + - apiGroups: [""] + resources: ["events"] + verbs: ["*"] - apiGroups: [""] resources: ["secrets"] verbs: ["get"] diff --git a/docs/Manual/Deployment/Kubernetes/BackupResource.md b/docs/Manual/Deployment/Kubernetes/BackupResource.md index aeb6755e0..b03923687 100644 --- a/docs/Manual/Deployment/Kubernetes/BackupResource.md +++ b/docs/Manual/Deployment/Kubernetes/BackupResource.md @@ -114,6 +114,8 @@ status: uploaded: true downloaded: true createdAt: "time" + sizeInBytes: 1 + numberOfDBServers: 3 available: true ``` @@ -429,6 +431,22 @@ Required: true Default: now() +#### `status.backup.sizeInBytes: uint64` + +Size of the Backup in ArangoDB. + +Required: true + +Default: 0 + +#### `status.backup.numberOfDBServers: uint` + +Cluster size of the Backup in ArangoDB. + +Required: true + +Default: 0 + ### `status.available: bool` Determines if we can restore from ArangoBackup. diff --git a/go.mod b/go.mod index 5d620200b..b1adf721a 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/aktau/github-release v0.7.2 // indirect github.com/arangodb-helper/go-certificates v0.0.0-20180821055445-9fca24fc2680 github.com/arangodb/arangosync-client v0.6.3 - github.com/arangodb/go-driver v0.0.0-20190917135832-4ec315b17bf0 + github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53 github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/bugagazavr/go-gitlab-client v0.0.0-20150830002541-e5999f934dc4 // indirect @@ -62,6 +62,7 @@ require ( github.com/juju/errgo v0.0.0-20140925100237-08cceb5d0b53 // indirect github.com/julienschmidt/httprouter v1.2.0 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 // indirect + github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect diff --git a/go.sum b/go.sum index 95df647d3..2020ac96c 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/arangodb/go-driver v0.0.0-20190806145426-48dd51aa0940 h1:CWLm7jOfT3v3 github.com/arangodb/go-driver v0.0.0-20190806145426-48dd51aa0940/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= github.com/arangodb/go-driver v0.0.0-20190917135832-4ec315b17bf0 h1:OZAHgwP/X6eJlm33G94lo32j82acWZZvB2d/hbSAbTs= github.com/arangodb/go-driver v0.0.0-20190917135832-4ec315b17bf0/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= +github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53 h1:gKOy8ZeLmAMWoCfZtXAwTpl9S3UxHjv8l4T96BvuXoQ= +github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21 h1:+W7D5ttxi/Ygh/39vialtypE23p9KI7P0J2qtoqUV4w= github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21/go.mod h1:RkPIG6JJ2pcJUoymc18NxAJGraZd+iAEVnOTDjZey/w= github.com/arangodb/go-velocypack v0.0.0-20190129082528-7896a965b4ad h1:Ah+VRYUWLuqgbfnDyuC8IrIe8XFzpt9tBVVnPGFNTJ8= @@ -414,6 +416,8 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFl github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a h1:+J2gw7Bw77w/fbK7wnNJJDKmw1IbWft2Ul5BzrG1Qm8= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= diff --git a/pkg/apis/backup/v1alpha/backup_state.go b/pkg/apis/backup/v1alpha/backup_state.go index 0b805059b..22057dc0b 100644 --- a/pkg/apis/backup/v1alpha/backup_state.go +++ b/pkg/apis/backup/v1alpha/backup_state.go @@ -41,22 +41,24 @@ const ( ArangoBackupStateReady state.State = "Ready" ArangoBackupStateDeleted state.State = "Deleted" ArangoBackupStateFailed state.State = "Failed" + ArangoBackupStateUnavailable state.State = "Unavailable" ) var ArangoBackupStateMap = state.Map{ - ArangoBackupStateNone: {ArangoBackupStatePending, ArangoBackupStateFailed}, + ArangoBackupStateNone: {ArangoBackupStatePending}, ArangoBackupStatePending: {ArangoBackupStateScheduled, ArangoBackupStateFailed}, ArangoBackupStateScheduled: {ArangoBackupStateDownload, ArangoBackupStateCreate, ArangoBackupStateFailed}, - ArangoBackupStateDownload: {ArangoBackupStateDownloading, ArangoBackupStateFailed}, + ArangoBackupStateDownload: {ArangoBackupStateDownloading, ArangoBackupStateFailed, ArangoBackupStateDownloadError}, ArangoBackupStateDownloading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateDownloadError}, ArangoBackupStateDownloadError: {ArangoBackupStatePending, ArangoBackupStateFailed}, ArangoBackupStateCreate: {ArangoBackupStateReady, ArangoBackupStateFailed}, - ArangoBackupStateUpload: {ArangoBackupStateUploading, ArangoBackupStateFailed}, + ArangoBackupStateUpload: {ArangoBackupStateUploading, ArangoBackupStateFailed, ArangoBackupStateDeleted, ArangoBackupStateUploadError}, ArangoBackupStateUploading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateUploadError}, ArangoBackupStateUploadError: {ArangoBackupStateFailed, ArangoBackupStateReady}, ArangoBackupStateReady: {ArangoBackupStateDeleted, ArangoBackupStateFailed, ArangoBackupStateUpload}, ArangoBackupStateDeleted: {ArangoBackupStateFailed}, ArangoBackupStateFailed: {ArangoBackupStatePending}, + ArangoBackupStateUnavailable: {ArangoBackupStateReady}, } type ArangoBackupState struct { @@ -72,7 +74,35 @@ type ArangoBackupState struct { Progress *ArangoBackupProgress `json:"progress,omitempty"` } +func (a *ArangoBackupState) Equal(b *ArangoBackupState) bool { + if a == b { + return true + } + + if a == nil && b != nil || a != nil && b == nil { + return false + } + + return a.State == b.State && + a.Time.Equal(&b.Time) && + a.Message == b.Message && + a.Progress.Equal(b.Progress) +} + type ArangoBackupProgress struct { JobID string `json:"jobID"` Progress string `json:"progress"` } + +func (a *ArangoBackupProgress) Equal(b *ArangoBackupProgress) bool { + if a == b { + return true + } + + if a == nil && b != nil || a != nil && b == nil { + return false + } + + return a.JobID == b.JobID && + a.Progress == b.Progress +} diff --git a/pkg/apis/backup/v1alpha/backup_status.go b/pkg/apis/backup/v1alpha/backup_status.go index b5cfdbab4..bc54d2f1a 100644 --- a/pkg/apis/backup/v1alpha/backup_status.go +++ b/pkg/apis/backup/v1alpha/backup_status.go @@ -34,12 +34,64 @@ type ArangoBackupStatus struct { Available bool `json:"available"` } +func (a *ArangoBackupStatus) Equal(b *ArangoBackupStatus) bool { + if a == b { + return true + } + + if a == nil && b != nil || a != nil && b == nil { + return false + } + + return a.ArangoBackupState.Equal(&b.ArangoBackupState) && + a.Backup.Equal(b.Backup) && + a.Available == b.Available +} + type ArangoBackupDetails struct { ID string `json:"id"` Version string `json:"version"` PotentiallyInconsistent *bool `json:"potentiallyInconsistent,omitempty"` + SizeInBytes uint64 `json:"sizeInBytes,omitempty"` + NumberOfDBServers uint `json:"numberOfDBServers,omitempty"` Uploaded *bool `json:"uploaded,omitempty"` Downloaded *bool `json:"downloaded,omitempty"` Imported *bool `json:"imported,omitempty"` CreationTimestamp meta.Time `json:"createdAt"` } + +func (a *ArangoBackupDetails) Equal(b *ArangoBackupDetails) bool { + if a == b { + return true + } + + if a == nil && b != nil || a != nil && b == nil { + return false + } + + return a.ID == b.ID && + a.Version == b.Version && + a.SizeInBytes == b.SizeInBytes && + a.NumberOfDBServers == b.NumberOfDBServers && + a.CreationTimestamp.Equal(&b.CreationTimestamp) && + compareBoolPointer(a.PotentiallyInconsistent, b.PotentiallyInconsistent) && + compareBoolPointer(a.Uploaded, b.Uploaded) && + compareBoolPointer(a.Downloaded, b.Downloaded) && + compareBoolPointer(a.Imported, b.Imported) +} + +func compareBoolPointer(a, b * bool) bool { + if a == nil && b != nil || a != nil && b == nil { + return false + } + + if a == b { + return true + } + + if *a == *b { + return true + } + + return false +} \ No newline at end of file diff --git a/pkg/backup/handlers/arango/backup/arango_client.go b/pkg/backup/handlers/arango/backup/arango_client.go index d7cccf635..09464b780 100644 --- a/pkg/backup/handlers/arango/backup/arango_client.go +++ b/pkg/backup/handlers/arango/backup/arango_client.go @@ -23,11 +23,8 @@ package backup import ( - "fmt" - "net/http" - "strings" - "github.com/arangodb/kube-arangodb/pkg/backup/utils" + "net/http" "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" @@ -75,59 +72,4 @@ type ArangoBackupClient interface { Delete(driver.BackupID) error List() (map[driver.BackupID]driver.BackupMeta, error) -} - -// NewTemporaryError created new temporary error -func NewTemporaryError(format string, a ...interface{}) error { - return TemporaryError{ - Message: fmt.Sprintf(format, a...), - } -} - -// TemporaryError defines error which will not update ArangoBackup object status -type TemporaryError struct { - Message string -} - -func (t TemporaryError) Error() string { - return t.Message -} - -// IsTemporaryError determined if error is type of TemporaryError -func IsTemporaryError(err error) bool { - _, ok := err.(TemporaryError) - return ok -} - -func checkTemporaryError(err error) bool { - if ok := IsTemporaryError(err); ok { - return true - } - - if v, ok := err.(utils.Temporary); ok { - if v.Temporary() { - return true - } - } - - if v, ok := err.(driver.ArangoError); ok { - if temporaryErrorNum.Has(v.ErrorNum) || temporaryCodes.Has(v.Code) { - return true - } - } - - if v, ok := err.(utils.Causer); ok { - return checkTemporaryError(v.Cause()) - } - - // Check error string - if strings.Contains(err.Error(), "context deadline exceeded") { - return true - } - - if strings.Contains(err.Error(), "connection refused") { - return true - } - - return false -} +} \ No newline at end of file diff --git a/pkg/backup/handlers/arango/backup/arango_client_mock_test.go b/pkg/backup/handlers/arango/backup/arango_client_mock_test.go index 81caee74f..bec4df82e 100644 --- a/pkg/backup/handlers/arango/backup/arango_client_mock_test.go +++ b/pkg/backup/handlers/arango/backup/arango_client_mock_test.go @@ -24,7 +24,9 @@ package backup import ( "fmt" + "math/rand" "sync" + "time" "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" @@ -42,14 +44,17 @@ func newMockArangoClientBackupErrorFactory(err error) ArangoClientFactory { } } -func newMockArangoClientBackupFactory(mock *mockArangoClientBackup) ArangoClientFactory { +func newMockArangoClientBackupFactory(mock *mockArangoClientBackupState) ArangoClientFactory { return func(deployment *database.ArangoDeployment, backup *backupApi.ArangoBackup) (ArangoBackupClient, error) { - return mock, nil + return &mockArangoClientBackup{ + backup: backup, + state: mock, + }, nil } } -func newMockArangoClientBackup(errors mockErrorsArangoClientBackup) *mockArangoClientBackup { - return &mockArangoClientBackup{ +func newMockArangoClientBackup(errors mockErrorsArangoClientBackup) *mockArangoClientBackupState { + return &mockArangoClientBackupState{ backups: map[driver.BackupID]driver.BackupMeta{}, progresses: map[driver.BackupTransferJobID]ArangoBackupProgress{}, errors: errors, @@ -57,13 +62,10 @@ func newMockArangoClientBackup(errors mockErrorsArangoClientBackup) *mockArangoC } type mockErrorsArangoClientBackup struct { - createError, listError, getError, uploadError, downloadError, progressError, existsError, deleteError, abortError string - isTemporaryError bool - - createForced bool + createError, listError, getError, uploadError, downloadError, progressError, existsError, deleteError, abortError error } -type mockArangoClientBackup struct { +type mockArangoClientBackupState struct { lock sync.Mutex backups map[driver.BackupID]driver.BackupMeta @@ -72,121 +74,115 @@ type mockArangoClientBackup struct { errors mockErrorsArangoClientBackup } +type mockArangoClientBackup struct { + backup *backupApi.ArangoBackup + state *mockArangoClientBackupState +} + func (m *mockArangoClientBackup) List() (map[driver.BackupID]driver.BackupMeta, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.listError); err != nil { - return nil, err + if m.state.errors.listError != nil { + return nil, m.state.errors.listError } - return m.backups, nil + return m.state.backups, nil } func (m *mockArangoClientBackup) Abort(d driver.BackupTransferJobID) error { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.abortError); err != nil { - return err + if m.state.errors.abortError != nil { + return m.state.errors.abortError } - if _, ok := m.progresses[d]; ok { - delete(m.progresses, d) + if _, ok := m.state.progresses[d]; ok { + delete(m.state.progresses, d) } return nil } func (m *mockArangoClientBackup) Exists(id driver.BackupID) (bool, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.existsError); err != nil { - return false, err + if m.state.errors.existsError != nil { + return false, m.state.errors.existsError } - _, ok := m.backups[id] + _, ok := m.state.backups[id] return ok, nil } func (m *mockArangoClientBackup) Delete(id driver.BackupID) error { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.deleteError); err != nil { - return err + if m.state.errors.deleteError != nil { + return m.state.errors.deleteError } - if _, ok := m.backups[id]; ok { - delete(m.backups, id) + if _, ok := m.state.backups[id]; ok { + delete(m.state.backups, id) } return nil } -func (m *mockArangoClientBackup) newError(msg string) error { - if msg == "" { - return nil - } - - if m.errors.isTemporaryError { - return NewTemporaryError(msg) - } - return fmt.Errorf(msg) -} - func (m *mockArangoClientBackup) Download(driver.BackupID) (driver.BackupTransferJobID, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.downloadError); err != nil { - return "", err + if m.state.errors.downloadError != nil { + return "", m.state.errors.downloadError } id := driver.BackupTransferJobID(uuid.NewUUID()) - m.progresses[id] = ArangoBackupProgress{} + m.state.progresses[id] = ArangoBackupProgress{} return id, nil } func (m *mockArangoClientBackup) Progress(id driver.BackupTransferJobID) (ArangoBackupProgress, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.progressError); err != nil { - return ArangoBackupProgress{}, err + if m.state.errors.progressError != nil { + return ArangoBackupProgress{}, m.state.errors.progressError } - return m.progresses[id], nil + return m.state.progresses[id], nil } func (m *mockArangoClientBackup) Upload(driver.BackupID) (driver.BackupTransferJobID, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.uploadError); err != nil { - return "", err + if m.state.errors.uploadError != nil { + return "", m.state.errors.uploadError } id := driver.BackupTransferJobID(uuid.NewUUID()) - m.progresses[id] = ArangoBackupProgress{} + m.state.progresses[id] = ArangoBackupProgress{} return id, nil } func (m *mockArangoClientBackup) Get(id driver.BackupID) (driver.BackupMeta, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.getError); err != nil { - return driver.BackupMeta{}, err + if m.state.errors.getError != nil { + return driver.BackupMeta{}, m.state.errors.getError } - if meta, ok := m.backups[id]; ok { + if meta, ok := m.state.backups[id]; ok { return meta, nil } @@ -194,32 +190,47 @@ func (m *mockArangoClientBackup) Get(id driver.BackupID) (driver.BackupMeta, err } func (m *mockArangoClientBackup) Create() (ArangoBackupCreateResponse, error) { - m.lock.Lock() - defer m.lock.Unlock() + m.state.lock.Lock() + defer m.state.lock.Unlock() - if err := m.newError(m.errors.createError); err != nil { - return ArangoBackupCreateResponse{}, err + if m.state.errors.createError != nil { + return ArangoBackupCreateResponse{}, m.state.errors.createError } id := driver.BackupID(uuid.NewUUID()) + inconsistent := false + + if m.backup != nil { + if m.backup.Spec.Options != nil { + if m.backup.Spec.Options.AllowInconsistent != nil { + inconsistent = *m.backup.Spec.Options.AllowInconsistent + } + } + } + meta := driver.BackupMeta{ - ID: id, - Version: mockVersion, + ID: id, + Version: mockVersion, + NumberOfDBServers: uint(rand.Uint32()), + DateTime: time.Now(), + SizeInBytes: rand.Uint64(), + PotentiallyInconsistent: inconsistent, + NumberOfFiles: uint(rand.Uint32()), } - m.backups[id] = meta + m.state.backups[id] = meta return ArangoBackupCreateResponse{ BackupMeta: meta, - PotentiallyInconsistent: m.errors.createForced, + PotentiallyInconsistent: meta.PotentiallyInconsistent, }, nil } func (m *mockArangoClientBackup) getIDs() []string { - ret := make([]string, 0, len(m.backups)) + ret := make([]string, 0, len(m.state.backups)) - for key := range m.backups { + for key := range m.state.backups { ret = append(ret, string(key)) } @@ -227,9 +238,9 @@ func (m *mockArangoClientBackup) getIDs() []string { } func (m *mockArangoClientBackup) getProgressIDs() []string { - ret := make([]string, 0, len(m.progresses)) + ret := make([]string, 0, len(m.state.progresses)) - for key := range m.progresses { + for key := range m.state.progresses { ret = append(ret, string(key)) } diff --git a/pkg/backup/handlers/arango/backup/arango_client_test.go b/pkg/backup/handlers/arango/backup/arango_client_test.go index 3dd59a2ee..0b12d80e9 100644 --- a/pkg/backup/handlers/arango/backup/arango_client_test.go +++ b/pkg/backup/handlers/arango/backup/arango_client_test.go @@ -100,7 +100,7 @@ func Test_Errors_Temporary(t *testing.T) { // Act for testName, c := range checks { t.Run(testName, func(t *testing.T) { - res := checkTemporaryError(c.err) + res := isTemporaryError(c.err) if c.temporary { require.True(t, res) } else { diff --git a/pkg/backup/handlers/arango/backup/backup_suite_test.go b/pkg/backup/handlers/arango/backup/backup_suite_test.go index f2434549f..15f92332d 100644 --- a/pkg/backup/handlers/arango/backup/backup_suite_test.go +++ b/pkg/backup/handlers/arango/backup/backup_suite_test.go @@ -24,6 +24,7 @@ package backup import ( "fmt" + "github.com/arangodb/go-driver" "testing" "github.com/arangodb/kube-arangodb/pkg/backup/operator/event" @@ -62,7 +63,10 @@ func newErrorsFakeHandler(errors mockErrorsArangoClientBackup) (*handler, *mockA mock := newMockArangoClientBackup(errors) handler.arangoClientFactory = newMockArangoClientBackupFactory(mock) - return handler, mock + return handler, &mockArangoClientBackup{ + backup: nil, + state: mock, + } } func newObjectSet(state state.State) (*backupApi.ArangoBackup, *database.ArangoDeployment) { @@ -77,7 +81,7 @@ func newObjectSet(state state.State) (*backupApi.ArangoBackup, *database.ArangoD func compareTemporaryState(t *testing.T, err error, errorMsg string, handler *handler, obj *backupApi.ArangoBackup) { require.Error(t, err) - require.True(t, IsTemporaryError(err)) + require.True(t, isTemporaryError(err)) require.EqualError(t, err, errorMsg) newObj := refreshArangoBackup(t, handler, obj) @@ -170,6 +174,21 @@ func createArangoDeployment(t *testing.T, h *handler, deployments ...*database.A } } +func compareBackupMeta(t *testing.T, backupMeta driver.BackupMeta, backup *backupApi.ArangoBackup) { + require.NotNil(t, backup.Status.Backup) + require.Equal(t, string(backupMeta.ID), backup.Status.Backup.ID) + require.Equal(t, backupMeta.PotentiallyInconsistent, *backup.Status.Backup.PotentiallyInconsistent) + require.Equal(t, backupMeta.SizeInBytes, backup.Status.Backup.SizeInBytes) + require.Equal(t, backupMeta.DateTime.UTC().Unix(), backup.Status.Backup.CreationTimestamp.Time.UTC().Unix()) + require.Equal(t, backupMeta.NumberOfDBServers, backup.Status.Backup.NumberOfDBServers) + require.Equal(t, backupMeta.Version, backup.Status.Backup.Version) +} + +func checkBackup(t *testing.T, backup *backupApi.ArangoBackup, state state.State, available bool) { + require.Equal(t, state, backup.Status.State) + require.Equal(t, available, backup.Status.Available) +} + func wrapperUndefinedDeployment(t *testing.T, state state.State) { t.Run("Empty Name", func(t *testing.T) { // Arrange @@ -186,7 +205,7 @@ func wrapperUndefinedDeployment(t *testing.T, state state.State) { newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) - require.Equal(t, newObj.Status.Message, createFailMessage(state, "deployment name can not be empty")) + require.Equal(t, newObj.Status.Message, createStateMessage(state, backupApi.ArangoBackupStateFailed, "deployment name can not be empty")) }) t.Run("Missing Deployment", func(t *testing.T) { @@ -203,7 +222,7 @@ func wrapperUndefinedDeployment(t *testing.T, state state.State) { newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) - require.Equal(t, newObj.Status.Message, createFailMessage(state, fmt.Sprintf("%s \"%s\" not found", database.ArangoDeploymentCRDName, obj.Name))) + require.Equal(t, newObj.Status.Message, createStateMessage(state, backupApi.ArangoBackupStateFailed, fmt.Sprintf("%s \"%s\" not found", database.ArangoDeploymentCRDName, obj.Name))) }) } @@ -224,7 +243,7 @@ func wrapperConnectionIssues(t *testing.T, state state.State) { // Assert require.Error(t, err) - require.True(t, IsTemporaryError(err)) + require.True(t, isTemporaryError(err)) newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status.State, state) @@ -248,7 +267,7 @@ func wrapperProgressMissing(t *testing.T, state state.State) { newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) - require.Equal(t, newObj.Status.Message, createFailMessage(state, fmt.Sprintf("backup details are missing"))) + require.Equal(t, newObj.Status.Message, createStateMessage(state, backupApi.ArangoBackupStateFailed, fmt.Sprintf("missing field .status.backup"))) }) } diff --git a/pkg/backup/handlers/arango/backup/errors.go b/pkg/backup/handlers/arango/backup/errors.go new file mode 100644 index 000000000..9ecf0e995 --- /dev/null +++ b/pkg/backup/handlers/arango/backup/errors.go @@ -0,0 +1,119 @@ +// +// DISCLAIMER +// +// Copyright 2018 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package backup + +import ( + "fmt" + "github.com/arangodb/go-driver" + "github.com/arangodb/kube-arangodb/pkg/backup/utils" + "strings" +) + +func newTemporaryError(err error) error { + return temporaryError{ + Causer:err, + } +} + +func newTemporaryErrorf(format string, a ... interface{}) error { + return newTemporaryError(fmt.Errorf(format, a...)) +} + +type temporaryError struct { + Causer error +} + +func (t temporaryError) Cause() error { + return t.Causer +} + +func (t temporaryError) Error() string { + return t.Causer.Error() +} + +func newFatalError(err error) error { + return fatalError{ + Causer:err, + } +} + +func newFatalErrorf(format string, a ... interface{}) error { + return newFatalError(fmt.Errorf(format, a...)) +} + +type fatalError struct { + Causer error +} + +func (f fatalError) Cause() error { + return f.Causer +} + +func (f fatalError) Error() string { + return f.Causer.Error() +} + +func isTemporaryError(err error) bool { + if _, ok := err.(temporaryError); ok { + return true + } + + if _, ok := err.(fatalError); ok { + return false + } + + if v, ok := err.(utils.Temporary); ok { + if v.Temporary() { + return true + } + } + + if v, ok := err.(driver.ArangoError); ok { + if temporaryErrorNum.Has(v.ErrorNum) || temporaryCodes.Has(v.Code) { + return true + } + } + + if v, ok := err.(utils.Causer); ok { + return isTemporaryError(v.Cause()) + } + + // Check error string + if strings.Contains(err.Error(), "context deadline exceeded") { + return true + } + + if strings.Contains(err.Error(), "connection refused") { + return true + } + + return false +} + +func switchError(err error) error { + if isTemporaryError(err) { + return newTemporaryError(err) + } + + return newFatalError(err) +} \ No newline at end of file diff --git a/pkg/backup/handlers/arango/backup/finalizer.go b/pkg/backup/handlers/arango/backup/finalizer.go index 7e3b91db9..dae109afe 100644 --- a/pkg/backup/handlers/arango/backup/finalizer.go +++ b/pkg/backup/handlers/arango/backup/finalizer.go @@ -28,6 +28,7 @@ import ( "github.com/arangodb/kube-arangodb/pkg/backup/utils" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) func (h *handler) finalize(backup *backupApi.ArangoBackup) error { @@ -68,6 +69,10 @@ func (h *handler) finalize(backup *backupApi.ArangoBackup) error { } func (h *handler) finalizeBackup(backup *backupApi.ArangoBackup) error { + lock := h.getDeploymentMutex(backup.Namespace, backup.Spec.Deployment.Name) + lock.Lock() + defer lock.Unlock() + if backup.Status.Backup == nil { // No details passed, object can be removed return nil @@ -80,9 +85,35 @@ func (h *handler) finalizeBackup(backup *backupApi.ArangoBackup) error { return nil } + if c, ok := err.(utils.Causer); ok { + if errors.IsNotFound(c.Cause()) { + return nil + } + } + + return err + } + + backups, err := h.client.BackupV1alpha().ArangoBackups(backup.Namespace).List(meta.ListOptions{}) + if err != nil { return err } + for _, existingBackup := range backups.Items { + if existingBackup.Name == backup.Name { + continue + } + + if existingBackup.Status.Backup == nil { + continue + } + + // This backup is still in use + if existingBackup.Status.Backup.ID == backup.Status.Backup.ID { + return nil + } + } + client, err := h.arangoClientFactory(deployment, backup) if err != nil { return err diff --git a/pkg/backup/handlers/arango/backup/handler.go b/pkg/backup/handlers/arango/backup/handler.go index c78011ff2..49238ef4c 100644 --- a/pkg/backup/handlers/arango/backup/handler.go +++ b/pkg/backup/handlers/arango/backup/handler.go @@ -25,7 +25,6 @@ package backup import ( "fmt" "github.com/arangodb/kube-arangodb/pkg/util" - "reflect" "sync" "time" @@ -178,19 +177,12 @@ func (h *handler) refreshDeploymentBackup(deployment *database.ArangoDeployment, return err } - backup.Status = backupApi.ArangoBackupStatus{ - Backup: &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - Imported: util.NewBool(true), - }, - Available: true, - ArangoBackupState: backupApi.ArangoBackupState{ - Time: meta.Now(), - State: backupApi.ArangoBackupStateReady, - }, - } + status := updateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, ""), + updateStatusBackup(backupMeta), + updateStatusBackupImported(util.NewBool(true))) + + backup.Status = *status err = h.updateBackupStatus(backup) if err != nil { @@ -288,6 +280,15 @@ func (h *handler) Handle(item operation.Item) error { return err } } + + b, err = h.client.BackupV1alpha().ArangoBackups(item.Namespace).Get(item.Name, meta.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + + return err + } } status, err := h.processArangoBackup(b.DeepCopy()) @@ -296,13 +297,22 @@ func (h *handler) Handle(item operation.Item) error { item.Kind, item.Namespace, item.Name) - return err + + cError := switchError(err) + + if _, ok := cError.(temporaryError); ok { + return cError + } + + status, _ = setFailedState(b, cError) } - status.Time = b.Status.Time + if status == nil { + return nil + } // Nothing to update, objects are equal - if reflect.DeepEqual(b.Status, status) { + if b.Status.Equal(status) { return nil } @@ -317,7 +327,6 @@ func (h *handler) Handle(item operation.Item) error { // Log message about state change if b.Status.State != status.State { - status.Time = meta.Now() if status.State == backupApi.ArangoBackupStateFailed { h.eventRecorder.Warning(b, StateChange, "Transiting from %s to %s with error: %s", b.Status.State, @@ -330,7 +339,7 @@ func (h *handler) Handle(item operation.Item) error { } } - b.Status = status + b.Status = *status log.Debug().Msgf("Updating %s %s/%s", item.Kind, @@ -345,16 +354,16 @@ func (h *handler) Handle(item operation.Item) error { return nil } -func (h *handler) processArangoBackup(backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func (h *handler) processArangoBackup(backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { if err := backup.Validate(); err != nil { - return createFailedState(err, backup.Status), nil + return setFailedState(backup, err) } if f, ok := stateHolders[backup.Status.State]; ok { return f(h, backup) } - return backupApi.ArangoBackupStatus{}, fmt.Errorf("state %s is not supported", backup.Status.State) + return nil, fmt.Errorf("state %s is not supported", backup.Status.State) } func (h *handler) CanBeHandled(item operation.Item) bool { @@ -365,8 +374,19 @@ func (h *handler) CanBeHandled(item operation.Item) bool { func (h *handler) getArangoDeploymentObject(backup *backupApi.ArangoBackup) (*database.ArangoDeployment, error) { if backup.Spec.Deployment.Name == "" { - return nil, fmt.Errorf("deployment ref is not specified for backup %s/%s", backup.Namespace, backup.Name) + return nil, newFatalErrorf("deployment ref is not specified for backup %s/%s", backup.Namespace, backup.Name) + } + + obj, err := h.client.DatabaseV1alpha().ArangoDeployments(backup.Namespace).Get(backup.Spec.Deployment.Name, meta.GetOptions{}) + if err == nil { + return obj, nil + } + + // Check if object is not found + if errors.IsNotFound(err) { + return nil, newFatalError(err) } - return h.client.DatabaseV1alpha().ArangoDeployments(backup.Namespace).Get(backup.Spec.Deployment.Name, meta.GetOptions{}) + // Otherwise it is connection issue - mark as temporary + return nil, newTemporaryError(err) } diff --git a/pkg/backup/handlers/arango/backup/state.go b/pkg/backup/handlers/arango/backup/state.go index 2a1189677..f1d0e0af4 100644 --- a/pkg/backup/handlers/arango/backup/state.go +++ b/pkg/backup/handlers/arango/backup/state.go @@ -27,7 +27,7 @@ import ( "github.com/arangodb/kube-arangodb/pkg/backup/state" ) -type stateHolder func(handler *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) +type stateHolder func(handler *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) var ( stateHolders = map[state.State]stateHolder{ diff --git a/pkg/backup/handlers/arango/backup/state_create.go b/pkg/backup/handlers/arango/backup/state_create.go index fe8be2f1e..851865635 100644 --- a/pkg/backup/handlers/arango/backup/state_create.go +++ b/pkg/backup/handlers/arango/backup/state_create.go @@ -23,43 +23,42 @@ package backup import ( + "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } - var details *backupApi.ArangoBackupDetails - - // Try to recover old backup. If old backup is missing go into deleted state - response, err := client.Create() if err != nil { - return switchTemporaryError(err, backup.Status) + return nil, err } - details = &backupApi.ArangoBackupDetails{ - ID: string(response.ID), - Version: response.Version, - CreationTimestamp: meta.Now(), - } + backupMeta, err := client.Get(response.ID) + if err != nil { + if driver.IsNotFound(err) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateFailed, + "backup is not present after creation"), + cleanStatusJob(), + ) + } - if response.PotentiallyInconsistent { - details.PotentiallyInconsistent = &response.PotentiallyInconsistent + return nil, newFatalError(err) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: details, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady,""), + updateStatusAvailable(true), + updateStatusBackup(backupMeta), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_create_test.go b/pkg/backup/handlers/arango/backup/state_create_test.go index 8c856d71a..303a09495 100644 --- a/pkg/backup/handlers/arango/backup/state_create_test.go +++ b/pkg/backup/handlers/arango/backup/state_create_test.go @@ -23,6 +23,8 @@ package backup import ( + "github.com/arangodb/go-driver" + "github.com/arangodb/kube-arangodb/pkg/util" "testing" "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" @@ -50,25 +52,25 @@ func Test_State_Create_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateReady) - - require.NotNil(t, newObj.Status.Backup) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) backups := mock.getIDs() require.Len(t, backups, 1) - require.Equal(t, newObj.Status.Backup.ID, backups[0]) - require.Equal(t, newObj.Status.Backup.Version, mockVersion) + backupMeta, err := mock.Get(driver.BackupID(backups[0])) + require.NoError(t, err) - require.Nil(t, newObj.Status.Backup.PotentiallyInconsistent) + compareBackupMeta(t, backupMeta, newObj) } func Test_State_Create_SuccessForced(t *testing.T) { // Arrange handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) - mock.errors.createForced = true obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate) + obj.Spec.Options = &backupApi.ArangoBackupSpecOptions{ + AllowInconsistent: util.NewBool(true), + } // Act createArangoDeployment(t, handler, deployment) @@ -78,19 +80,17 @@ func Test_State_Create_SuccessForced(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateReady) - - require.NotNil(t, newObj.Status.Backup) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) backups := mock.getIDs() require.Len(t, backups, 1) - require.Equal(t, newObj.Status.Backup.ID, backups[0]) - require.Equal(t, newObj.Status.Backup.Version, mockVersion) + backupMeta, err := mock.Get(driver.BackupID(backups[0])) + require.NoError(t, err) + compareBackupMeta(t, backupMeta, newObj) require.NotNil(t, newObj.Status.Backup.PotentiallyInconsistent) - value := *newObj.Status.Backup.PotentiallyInconsistent - require.True(t, value) + require.True(t, *newObj.Status.Backup.PotentiallyInconsistent) } func Test_State_Create_Upload(t *testing.T) { @@ -110,24 +110,22 @@ func Test_State_Create_Upload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateReady) - - require.NotNil(t, newObj.Status.Backup) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) backups := mock.getIDs() require.Len(t, backups, 1) - require.Equal(t, newObj.Status.Backup.ID, backups[0]) - require.Equal(t, newObj.Status.Backup.Version, mockVersion) + backupMeta, err := mock.Get(driver.BackupID(backups[0])) + require.NoError(t, err) - require.True(t, newObj.Status.Available) + compareBackupMeta(t, backupMeta, newObj) } func Test_State_Create_CreateFailed(t *testing.T) { // Arrange - errorMsg := "create error" + error := newFatalErrorf("error") handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - createError: errorMsg, + createError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate) @@ -141,7 +139,7 @@ func Test_State_Create_CreateFailed(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) - require.Equal(t, newObj.Status.Message, createFailMessage(backupApi.ArangoBackupStateCreate, errorMsg)) + require.Equal(t, newObj.Status.Message, createStateMessage(backupApi.ArangoBackupStateCreate, backupApi.ArangoBackupStateFailed, error.Error())) require.Nil(t, newObj.Status.Backup) @@ -150,10 +148,9 @@ func Test_State_Create_CreateFailed(t *testing.T) { func Test_State_Create_TemporaryCreateFailed(t *testing.T) { // Arrange - errorMsg := "create error" + error := newTemporaryErrorf("error") handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - isTemporaryError: true, - createError: errorMsg, + createError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate) @@ -163,7 +160,10 @@ func Test_State_Create_TemporaryCreateFailed(t *testing.T) { createArangoBackup(t, handler, obj) err := handler.Handle(newItemFromBackup(operation.Update, obj)) + require.EqualError(t, err, error.Error()) // Assert - compareTemporaryState(t, err, errorMsg, handler, obj) + newObj := refreshArangoBackup(t, handler, obj) + + require.Equal(t, obj.Status, newObj.Status) } diff --git a/pkg/backup/handlers/arango/backup/state_deleted.go b/pkg/backup/handlers/arango/backup/state_deleted.go index 78af7ccf6..83f5ed1fc 100644 --- a/pkg/backup/handlers/arango/backup/state_deleted.go +++ b/pkg/backup/handlers/arango/backup/state_deleted.go @@ -26,6 +26,6 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateDeletedHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { - return backup.Status, nil +func stateDeletedHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + return wrapUpdateStatus(backup) } diff --git a/pkg/backup/handlers/arango/backup/state_download.go b/pkg/backup/handlers/arango/backup/state_download.go index 8a1eeefc4..9a0f67a3b 100644 --- a/pkg/backup/handlers/arango/backup/state_download.go +++ b/pkg/backup/handlers/arango/backup/state_download.go @@ -23,43 +23,53 @@ package backup import ( - "fmt" - "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateDownloadHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateDownloadHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } if backup.Spec.Download == nil { - return createFailedState(fmt.Errorf("missing field .spec.download"), backup.Status), nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "missing field .spec.download"), + cleanStatusJob(), + updateStatusAvailable(false), + ) } if backup.Spec.Download.ID == "" { - return createFailedState(fmt.Errorf("missing field .spec.download.id"), backup.Status), nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "missing field .spec.download.id"), + cleanStatusJob(), + updateStatusAvailable(false), + ) } jobID, err := client.Download(driver.BackupID(backup.Spec.Download.ID)) if err != nil { - return switchTemporaryError(err, backup.Status) + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "missing field .spec.download.id"), + cleanStatusJob(), + updateStatusAvailable(false), + ) } - return backupApi.ArangoBackupStatus{ - Available: false, - ArangoBackupState: newState(backupApi.ArangoBackupStateDownloading, "", - &backupApi.ArangoBackupProgress{ - JobID: string(jobID), - Progress: "0%", - }), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloading, ""), + updateStatusJob(string(jobID), "0%"), + updateStatusAvailable(false), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_download_test.go b/pkg/backup/handlers/arango/backup/state_download_test.go index 107162f8a..dff1fafe2 100644 --- a/pkg/backup/handlers/arango/backup/state_download_test.go +++ b/pkg/backup/handlers/arango/backup/state_download_test.go @@ -56,24 +56,22 @@ func Test_State_Download_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateDownloading) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloading, false) require.NotNil(t, newObj.Status.Progress) progresses := mock.getProgressIDs() require.Len(t, progresses, 1) require.Equal(t, progresses[0], newObj.Status.Progress.JobID) - require.False(t, newObj.Status.Available) - require.Nil(t, newObj.Status.Backup) } // Check version func Test_State_Download_DownloadFailed(t *testing.T) { // Arrange - errorMsg := "download error" + error := newFatalErrorf("error") handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - downloadError: errorMsg, + downloadError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownload) @@ -93,23 +91,20 @@ func Test_State_Download_DownloadFailed(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloadError, false) require.Nil(t, newObj.Status.Progress) progresses := mock.getProgressIDs() require.Len(t, progresses, 0) - require.False(t, newObj.Status.Available) - require.Nil(t, newObj.Status.Backup) } func Test_State_Download_TemporaryDownloadFailed(t *testing.T) { // Arrange - errorMsg := "download error" - handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - isTemporaryError: true, - downloadError: errorMsg, + error := newTemporaryErrorf("error") + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + downloadError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownload) @@ -125,8 +120,15 @@ func Test_State_Download_TemporaryDownloadFailed(t *testing.T) { createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - err := handler.Handle(newItemFromBackup(operation.Update, obj)) + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) // Assert - compareTemporaryState(t, err, errorMsg, handler, obj) + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloadError, false) + + require.Nil(t, newObj.Status.Progress) + progresses := mock.getProgressIDs() + require.Len(t, progresses, 0) + + require.Nil(t, newObj.Status.Backup) } diff --git a/pkg/backup/handlers/arango/backup/state_downloaderror.go b/pkg/backup/handlers/arango/backup/state_downloaderror.go index cd5147035..ba0e1f115 100644 --- a/pkg/backup/handlers/arango/backup/state_downloaderror.go +++ b/pkg/backup/handlers/arango/backup/state_downloaderror.go @@ -32,17 +32,12 @@ const ( downloadDelay = time.Minute ) -func stateDownloadErrorHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateDownloadErrorHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { // Start again download if backup.Status.Time.Time.Add(downloadDelay).Before(time.Now()) { - return backupApi.ArangoBackupStatus{ - Available: false, - ArangoBackupState: newState(backupApi.ArangoBackupStatePending, "", nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStatePending, "")) } - return backupApi.ArangoBackupStatus{ - Available: false, - ArangoBackupState: backup.Status.ArangoBackupState, - }, nil + return wrapUpdateStatus(backup) } diff --git a/pkg/backup/handlers/arango/backup/state_downloaderror_test.go b/pkg/backup/handlers/arango/backup/state_downloaderror_test.go index e02575c8f..e0c5d084d 100644 --- a/pkg/backup/handlers/arango/backup/state_downloaderror_test.go +++ b/pkg/backup/handlers/arango/backup/state_downloaderror_test.go @@ -55,9 +55,7 @@ func Test_State_DownloadError_Reschedule(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStatePending) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStatePending, false) require.Nil(t, newObj.Status.Backup) } @@ -86,9 +84,8 @@ func Test_State_DownloadError_Wait(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateDownloadError) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloadError, false) - require.False(t, newObj.Status.Available) require.Equal(t, "message", newObj.Status.Message) require.Nil(t, newObj.Status.Backup) diff --git a/pkg/backup/handlers/arango/backup/state_downloading.go b/pkg/backup/handlers/arango/backup/state_downloading.go index e5f2d97dc..dd54cee0f 100644 --- a/pkg/backup/handlers/arango/backup/state_downloading.go +++ b/pkg/backup/handlers/arango/backup/state_downloading.go @@ -26,72 +26,79 @@ import ( "fmt" "github.com/arangodb/kube-arangodb/pkg/util" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateDownloadingHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateDownloadingHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } if backup.Status.Progress == nil { - return createFailedState(fmt.Errorf("backup progress details are missing"), backup.Status), nil + return nil, newFatalErrorf("backup progress details are missing") } if backup.Spec.Download == nil { - return createFailedState(fmt.Errorf("missing field .spec.download"), backup.Status), nil + return nil, newFatalErrorf("missing field .spec.download") } if backup.Spec.Download.ID == "" { - return createFailedState(fmt.Errorf("missing field .spec.download.id"), backup.Status), nil + return nil, newFatalErrorf("missing field .spec.download.id") } details, err := client.Progress(driver.BackupTransferJobID(backup.Status.Progress.JobID)) if err != nil { - return backup.Status, nil + if driver.IsNotFound(err) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "job with id %s does not exist anymore", backup.Status.Progress.JobID), + cleanStatusJob(), + ) + } + + return nil, newTemporaryError(err) } if details.Failed { - return backupApi.ArangoBackupStatus{ - Available: false, - ArangoBackupState: newState(backupApi.ArangoBackupStateDownloadError, - fmt.Sprintf("Download failed with error: %s", details.FailMessage), nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "Download failed with error: %s", details.FailMessage), + cleanStatusJob(), + ) } if details.Completed { backupMeta, err := client.Get(driver.BackupID(backup.Spec.Download.ID)) if err != nil { - return switchTemporaryError(err, backup.Status) + if driver.IsNotFound(err) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloadError, + "backup is not present after download"), + cleanStatusJob(), + ) + } + + return nil, newTemporaryError(err) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - Downloaded: util.NewBool(true), - }, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady,""), + updateStatusAvailable(true), + updateStatusBackup(backupMeta), + updateStatusBackupDownload(util.NewBool(true)), + cleanStatusJob(), + ) } - return backupApi.ArangoBackupStatus{ - Available: false, - ArangoBackupState: newState(backupApi.ArangoBackupStateDownloading, "", - &backupApi.ArangoBackupProgress{ - JobID: backup.Status.Progress.JobID, - Progress: fmt.Sprintf("%d%%", details.Progress), - }), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownloading,""), + updateStatusJob(backup.Status.Progress.JobID, fmt.Sprintf("%d%%", details.Progress)), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_downloading_test.go b/pkg/backup/handlers/arango/backup/state_downloading_test.go index 6789e5614..a54370c12 100644 --- a/pkg/backup/handlers/arango/backup/state_downloading_test.go +++ b/pkg/backup/handlers/arango/backup/state_downloading_test.go @@ -24,12 +24,12 @@ package backup import ( "fmt" + "github.com/arangodb/go-driver" "testing" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" "github.com/stretchr/testify/require" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -47,17 +47,16 @@ func Test_State_Downloading_Success(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownloading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Download(backupMeta.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ @@ -79,16 +78,15 @@ func Test_State_Downloading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateDownloading, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloading, false) + require.NotNil(t, newObj.Status.Progress) require.Equal(t, fmt.Sprintf("%d%%", 0), newObj.Status.Progress.Progress) require.Equal(t, obj.Status.Progress.JobID, newObj.Status.Progress.JobID) - - require.False(t, newObj.Status.Available) }) t.Run("Restore percent after update", func(t *testing.T) { p := 55 - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Progress: p, } @@ -96,15 +94,14 @@ func Test_State_Downloading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateDownloading, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloading, false) + require.NotNil(t, newObj.Status.Progress) require.Equal(t, fmt.Sprintf("%d%%", p), newObj.Status.Progress.Progress) require.Equal(t, string(progress), newObj.Status.Progress.JobID) - - require.False(t, newObj.Status.Available) }) t.Run("Finished", func(t *testing.T) { - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Completed: true, } @@ -112,7 +109,7 @@ func Test_State_Downloading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) require.Nil(t, newObj.Status.Progress) require.True(t, newObj.Status.Available) @@ -127,23 +124,22 @@ func Test_State_Downloading_FailedDownload(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownloading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Download(backupMeta.ID) require.NoError(t, err) errorMsg := errorString - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Failed: true, FailMessage: errorMsg, } - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ @@ -164,33 +160,30 @@ func Test_State_Downloading_FailedDownload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateDownloadError, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloadError, false) require.Equal(t, fmt.Sprintf("Download failed with error: %s", errorMsg), newObj.Status.Message) require.Nil(t, newObj.Status.Progress) - - require.False(t, newObj.Status.Available) } func Test_State_Downloading_FailedProgress(t *testing.T) { // Arrange - errorMsg := progressError + error := newFatalErrorf("error") handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - progressError: errorMsg, + progressError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownloading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Download(backupMeta.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ @@ -207,37 +200,32 @@ func Test_State_Downloading_FailedProgress(t *testing.T) { createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateDownloading, newObj.Status.State) - require.NotNil(t, newObj.Status.Progress) - - require.False(t, newObj.Status.Available) + require.Equal(t, obj.Status, newObj.Status) } func Test_State_Downloading_TemporaryFailedProgress(t *testing.T) { // Arrange - errorMsg := progressError + error := newTemporaryErrorf("error") handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - isTemporaryError: true, - progressError: errorMsg, + progressError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownloading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Download(backupMeta.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ @@ -254,12 +242,55 @@ func Test_State_Downloading_TemporaryFailedProgress(t *testing.T) { createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - err = handler.Handle(newItemFromBackup(operation.Update, obj)) + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateDownloading, newObj.Status.State) - require.NotNil(t, newObj.Status.Progress) - - require.False(t, newObj.Status.Available) + require.Equal(t, obj.Status, newObj.Status) } + +func Test_State_Downloading_NotFoundProgress(t *testing.T) { + // Arrange + error := driver.ArangoError{ + Code: 404, + } + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + progressError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateDownloading) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + progress, err := mock.Download(backupMeta.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ + ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ + RepositoryURL: "S3 URL", + }, + ID: string(backupMeta.ID), + } + + obj.Status.Progress = &backupApi.ArangoBackupProgress{ + JobID: string(progress), + } + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownloadError, false) + require.Equal(t, fmt.Sprintf("job with id %s does not exist anymore", progress), newObj.Status.Message) + require.Nil(t, newObj.Status.Progress) +} \ No newline at end of file diff --git a/pkg/backup/handlers/arango/backup/state_failed.go b/pkg/backup/handlers/arango/backup/state_failed.go index 2926f233b..ea0b12a2e 100644 --- a/pkg/backup/handlers/arango/backup/state_failed.go +++ b/pkg/backup/handlers/arango/backup/state_failed.go @@ -26,6 +26,6 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateFailedHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { - return backup.Status, nil +func stateFailedHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + return wrapUpdateStatus(backup) } diff --git a/pkg/backup/handlers/arango/backup/state_none.go b/pkg/backup/handlers/arango/backup/state_none.go index 3790d77b5..32f9bdf54 100644 --- a/pkg/backup/handlers/arango/backup/state_none.go +++ b/pkg/backup/handlers/arango/backup/state_none.go @@ -26,8 +26,7 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateNoneHandler(*handler, *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { - return backupApi.ArangoBackupStatus{ - ArangoBackupState: newState(backupApi.ArangoBackupStatePending, "", nil), - }, nil +func stateNoneHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStatePending, "")) } diff --git a/pkg/backup/handlers/arango/backup/state_none_test.go b/pkg/backup/handlers/arango/backup/state_none_test.go index 1ed787bb8..d0eac3d1e 100644 --- a/pkg/backup/handlers/arango/backup/state_none_test.go +++ b/pkg/backup/handlers/arango/backup/state_none_test.go @@ -42,7 +42,5 @@ func Test_State_None_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStatePending) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStatePending, false) } diff --git a/pkg/backup/handlers/arango/backup/state_pending.go b/pkg/backup/handlers/arango/backup/state_pending.go index 672788cc3..e6352eaf6 100644 --- a/pkg/backup/handlers/arango/backup/state_pending.go +++ b/pkg/backup/handlers/arango/backup/state_pending.go @@ -26,24 +26,22 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func statePendingHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func statePendingHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { _, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } running, err := isBackupRunning(backup, h.client.BackupV1alpha().ArangoBackups(backup.Namespace)) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } if running { - return backupApi.ArangoBackupStatus{ - ArangoBackupState: newState(backupApi.ArangoBackupStatePending, "backup already in process", nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStatePending, "backup already in process")) } - return backupApi.ArangoBackupStatus{ - ArangoBackupState: newState(backupApi.ArangoBackupStateScheduled, "", nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateScheduled, "")) } diff --git a/pkg/backup/handlers/arango/backup/state_pending_test.go b/pkg/backup/handlers/arango/backup/state_pending_test.go index 1c98f152d..5aa435f64 100644 --- a/pkg/backup/handlers/arango/backup/state_pending_test.go +++ b/pkg/backup/handlers/arango/backup/state_pending_test.go @@ -55,9 +55,11 @@ func Test_State_Pending_CheckNamespaceIsolation(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) + checkBackup(t, newObj, backupApi.ArangoBackupStateFailed, false) - require.Equal(t, newObj.Status.Message, createFailMessage(backupApi.ArangoBackupStatePending, fmt.Sprintf("%s \"%s\" not found", database.ArangoDeploymentCRDName, obj.Name))) + require.Equal(t, newObj.Status.Message, + createStateMessage(backupApi.ArangoBackupStatePending, backupApi.ArangoBackupStateFailed, + fmt.Sprintf("%s \"%s\" not found", database.ArangoDeploymentCRDName, obj.Name))) } func Test_State_Pending_OneBackupObject(t *testing.T) { @@ -74,9 +76,7 @@ func Test_State_Pending_OneBackupObject(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateScheduled) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateScheduled, false) } func Test_State_Pending_MultipleBackupObjectWithLimitation(t *testing.T) { @@ -97,9 +97,7 @@ func Test_State_Pending_MultipleBackupObjectWithLimitation(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateScheduled) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateScheduled, false) }) t.Run("Second backup object", func(t *testing.T) { @@ -107,7 +105,7 @@ func Test_State_Pending_MultipleBackupObjectWithLimitation(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj2) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStatePending) + checkBackup(t, newObj, backupApi.ArangoBackupStatePending, false) require.Equal(t, newObj.Status.Message, "backup already in process") }) } diff --git a/pkg/backup/handlers/arango/backup/state_ready.go b/pkg/backup/handlers/arango/backup/state_ready.go index 5ba31db48..0711d558a 100644 --- a/pkg/backup/handlers/arango/backup/state_ready.go +++ b/pkg/backup/handlers/arango/backup/state_ready.go @@ -23,39 +23,37 @@ package backup import ( - "fmt" - "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateReadyHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateReadyHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } if backup.Status.Backup == nil { - return createFailedState(fmt.Errorf("backup details are missing"), backup.Status), nil + return nil, newFatalErrorf("missing field .status.backup") } - _, err = client.Get(driver.BackupID(backup.Status.Backup.ID)) + backupMeta, err := client.Get(driver.BackupID(backup.Status.Backup.ID)) if err != nil { if driver.IsNotFound(err) { - return backupApi.ArangoBackupStatus{ - ArangoBackupState: backupApi.ArangoBackupState{ - State: backupApi.ArangoBackupStateDeleted, - }, - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDeleted, ""), + updateStatusAvailable(false), + ) } - return backup.Status, nil + return wrapUpdateStatus(backup, + updateStatusAvailable(true), + ) } // Check if upload flag was specified later in runtime @@ -64,38 +62,35 @@ func stateReadyHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.Ar // Ensure that we can start upload process running, err := isBackupRunning(backup, h.client.BackupV1alpha().ArangoBackups(backup.Namespace)) if err != nil { - return backup.Status, nil + return nil, err } if running { - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "Upload process queued", nil), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, "Upload process queued"), + updateStatusBackup(backupMeta), + updateStatusAvailable(true), + ) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateUpload, "", nil), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUpload, ""), + updateStatusBackup(backupMeta), + updateStatusAvailable(true), + ) } - // Remove old upload flag if backup.Spec.Upload == nil && backup.Status.Backup.Uploaded != nil { - newBackup := backup.Status.Backup.DeepCopy() - newBackup.Uploaded = nil - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: newBackup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, ""), + updateStatusBackup(backupMeta), + updateStatusBackupUpload(nil), + updateStatusAvailable(true), + ) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusBackup(backupMeta), + updateStatusAvailable(true), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_ready_test.go b/pkg/backup/handlers/arango/backup/state_ready_test.go index c2457a664..50e15a3fd 100644 --- a/pkg/backup/handlers/arango/backup/state_ready_test.go +++ b/pkg/backup/handlers/arango/backup/state_ready_test.go @@ -23,17 +23,16 @@ package backup import ( + "github.com/arangodb/go-driver" "github.com/arangodb/kube-arangodb/pkg/util" + "k8s.io/apimachinery/pkg/util/uuid" "sync" "testing" - "k8s.io/apimachinery/pkg/util/uuid" - "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" "github.com/stretchr/testify/require" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_State_Ready_Common(t *testing.T) { @@ -47,15 +46,13 @@ func Test_State_Ready_Success(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) // Act createArangoDeployment(t, handler, deployment) @@ -65,28 +62,70 @@ func Test_State_Ready_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, obj.Status, newObj.Status) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) } -func Test_State_Ready_GetFailed(t *testing.T) { +func Test_State_Ready_Success_Update(t *testing.T) { // Arrange - errorMsg := "get error" - handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - getError: errorMsg, - }) + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + t.Run("First iteration", func(t *testing.T) { + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) + }) + + t.Run("Second iteration", func(t *testing.T) { + backupMeta.SizeInBytes = 123 + mock.state.backups[backupMeta.ID] = backupMeta + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) + require.Equal(t, uint64(123), newObj.Status.Backup.SizeInBytes) + }) + + t.Run("Do nothing", func(t *testing.T) { + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) + }) +} + +func Test_State_Ready_TemporaryGetFailed(t *testing.T) { + // Arrange + error := newTemporaryErrorf("error") + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) + + obj.Status.Backup = &backupApi.ArangoBackupDetails{} // Act createArangoDeployment(t, handler, deployment) @@ -96,40 +135,53 @@ func Test_State_Ready_GetFailed(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) } -func Test_State_Ready_TemporaryGetFailed(t *testing.T) { +func Test_State_Ready_FatalGetFailed(t *testing.T) { // Arrange - errorMsg := "get error" - handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - isTemporaryError: true, - getError: errorMsg, + error := newFatalErrorf("error") + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) - backupMeta, err := mock.Create() - require.NoError(t, err) + obj.Status.Backup = &backupApi.ArangoBackupDetails{} + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) +} + +func Test_State_Ready_MissingBackup(t *testing.T) { + // Arrange + error := driver.ArangoError{ + Code: 404, } - obj.Status.Available = true + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) + + obj.Status.Backup = &backupApi.ArangoBackupDetails{} // Act createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - err = handler.Handle(newItemFromBackup(operation.Update, obj)) + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateDeleted, false) } func Test_State_Ready_Upload(t *testing.T) { @@ -141,15 +193,13 @@ func Test_State_Ready_Upload(t *testing.T) { RepositoryURL: "Any", } - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) // Act createArangoDeployment(t, handler, deployment) @@ -159,8 +209,7 @@ func Test_State_Ready_Upload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUpload, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateUpload, true) } func Test_State_Ready_DownloadDoNothing(t *testing.T) { @@ -175,15 +224,15 @@ func Test_State_Ready_DownloadDoNothing(t *testing.T) { ID: "some", } - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, &backupApi.ArangoBackupDetails{ + Downloaded: util.NewBool(true), + }) // Act createArangoDeployment(t, handler, deployment) @@ -193,8 +242,9 @@ func Test_State_Ready_DownloadDoNothing(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + require.NotNil(t, newObj.Status.Backup.Downloaded) + require.True(t, *newObj.Status.Backup.Downloaded) } func Test_State_Ready_DoUploadDownloadedBackup(t *testing.T) { @@ -206,17 +256,16 @@ func Test_State_Ready_DoUploadDownloadedBackup(t *testing.T) { RepositoryURL: "Any", } - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - Downloaded: util.NewBool(true), - Uploaded: util.NewBool(false), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, &backupApi.ArangoBackupDetails{ + Downloaded: util.NewBool(true), + Uploaded: util.NewBool(false), + }) // Act createArangoDeployment(t, handler, deployment) @@ -226,8 +275,9 @@ func Test_State_Ready_DoUploadDownloadedBackup(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUpload, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateUpload, true) + require.NotNil(t, newObj.Status.Backup.Downloaded) + require.True(t, *newObj.Status.Backup.Downloaded) } func Test_State_Ready_DoNotReUploadBackup(t *testing.T) { @@ -239,16 +289,15 @@ func Test_State_Ready_DoNotReUploadBackup(t *testing.T) { RepositoryURL: "Any", } - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - Uploaded: util.NewBool(true), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, &backupApi.ArangoBackupDetails{ + Uploaded: util.NewBool(true), + }) // Act createArangoDeployment(t, handler, deployment) @@ -258,8 +307,9 @@ func Test_State_Ready_DoNotReUploadBackup(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + require.NotNil(t, newObj.Status.Backup.Uploaded) + require.True(t, *newObj.Status.Backup.Uploaded) } func Test_State_Ready_RemoveUploadedFlag(t *testing.T) { @@ -268,16 +318,15 @@ func Test_State_Ready_RemoveUploadedFlag(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - Uploaded: util.NewBool(true), - } - obj.Status.Available = true + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, &backupApi.ArangoBackupDetails{ + Uploaded: util.NewBool(true), + }) // Act createArangoDeployment(t, handler, deployment) @@ -287,8 +336,7 @@ func Test_State_Ready_RemoveUploadedFlag(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) require.Nil(t, newObj.Status.Backup.Uploaded) } @@ -302,16 +350,16 @@ func Test_State_Ready_KeepPendingWithForcedRunning(t *testing.T) { size := 128 objects := make([]*backupApi.ArangoBackup, size) for id := range objects { - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) obj := newArangoBackup(name, name, string(uuid.NewUUID()), backupApi.ArangoBackupStateReady) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + obj.Spec.Upload = &backupApi.ArangoBackupSpecOperation{ RepositoryURL: "s3://test", } @@ -362,7 +410,10 @@ func Test_State_Ready_KeepPendingWithForcedRunningSameId(t *testing.T) { name := string(uuid.NewUUID()) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) deployment := newArangoDeployment(name, name) @@ -372,11 +423,7 @@ func Test_State_Ready_KeepPendingWithForcedRunningSameId(t *testing.T) { obj := newArangoBackup(name, name, string(uuid.NewUUID()), backupApi.ArangoBackupStateReady) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Spec.Upload = &backupApi.ArangoBackupSpecOperation{ RepositoryURL: "s3://test", } diff --git a/pkg/backup/handlers/arango/backup/state_scheduled.go b/pkg/backup/handlers/arango/backup/state_scheduled.go index 34af3d41b..3aaf2fa03 100644 --- a/pkg/backup/handlers/arango/backup/state_scheduled.go +++ b/pkg/backup/handlers/arango/backup/state_scheduled.go @@ -26,19 +26,18 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateScheduledHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateScheduledHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + // If unable to get ArangoDeployment go into Failed state _, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } if backup.Spec.Download != nil { - return backupApi.ArangoBackupStatus{ - ArangoBackupState: newState(backupApi.ArangoBackupStateDownload, "", nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDownload, "")) } - return backupApi.ArangoBackupStatus{ - ArangoBackupState: newState(backupApi.ArangoBackupStateCreate, "", nil), - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateCreate, "")) } diff --git a/pkg/backup/handlers/arango/backup/state_scheduled_test.go b/pkg/backup/handlers/arango/backup/state_scheduled_test.go index 22afcef48..333e7bc6f 100644 --- a/pkg/backup/handlers/arango/backup/state_scheduled_test.go +++ b/pkg/backup/handlers/arango/backup/state_scheduled_test.go @@ -56,9 +56,7 @@ func Test_State_Scheduled_Download(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateDownload) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateDownload, false) } func Test_State_Scheduled_Create(t *testing.T) { @@ -75,9 +73,7 @@ func Test_State_Scheduled_Create(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateCreate) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateCreate, false) } func Test_State_Scheduled_Upload(t *testing.T) { @@ -98,7 +94,5 @@ func Test_State_Scheduled_Upload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateCreate) - - require.False(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateCreate, false) } diff --git a/pkg/backup/handlers/arango/backup/state_upload.go b/pkg/backup/handlers/arango/backup/state_upload.go index 0ed6e14cc..9bcba6c27 100644 --- a/pkg/backup/handlers/arango/backup/state_upload.go +++ b/pkg/backup/handlers/arango/backup/state_upload.go @@ -23,44 +23,51 @@ package backup import ( - "fmt" - "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateUploadHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateUploadHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } if backup.Status.Backup == nil { - return createFailedState(fmt.Errorf("backup details are missing"), backup.Status), nil + return nil, newFatalErrorf("missing field .status.backup") } meta, err := client.Get(driver.BackupID(backup.Status.Backup.ID)) if err != nil { - return switchTemporaryError(err, backup.Status) + if driver.IsNotFound(err) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDeleted, ""), + updateStatusAvailable(false), + ) + } + + return nil, newTemporaryError(err) } jobID, err := client.Upload(meta.ID) if err != nil { - return switchTemporaryError(err, backup.Status) + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUploadError, + "Upload failed with error: %s", err.Error()), + cleanStatusJob(), + updateStatusBackupUpload(nil), + updateStatusAvailable(true), + ) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateUploading, "", - &backupApi.ArangoBackupProgress{ - JobID: string(jobID), - Progress: "0%", - }), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUploading, ""), + updateStatusJob(string(jobID), "0%"), + updateStatusAvailable(true), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_upload_test.go b/pkg/backup/handlers/arango/backup/state_upload_test.go index 4aa70e139..ff24323b8 100644 --- a/pkg/backup/handlers/arango/backup/state_upload_test.go +++ b/pkg/backup/handlers/arango/backup/state_upload_test.go @@ -23,18 +23,13 @@ package backup import ( + "github.com/arangodb/go-driver" "testing" "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" "github.com/stretchr/testify/require" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - getError = "get error" - uploadError = "upload error" ) func Test_State_Upload_Common(t *testing.T) { @@ -48,14 +43,13 @@ func Test_State_Upload_Success(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) // Act createArangoDeployment(t, handler, deployment) @@ -65,110 +59,149 @@ func Test_State_Upload_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateUploading) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploading, true) require.NotNil(t, newObj.Status.Progress) progresses := mock.getProgressIDs() require.Len(t, progresses, 1) require.Equal(t, progresses[0], newObj.Status.Progress.JobID) - require.True(t, newObj.Status.Available) - - require.NotNil(t, newObj.Status.Backup) - require.Equal(t, string(backupMeta.ID), newObj.Status.Backup.ID) - require.Equal(t, backupMeta.Version, newObj.Status.Backup.Version) + compareBackupMeta(t, backupMeta, obj) } -func Test_State_Upload_Failed(t *testing.T) { +func Test_State_Upload_TemporaryGetFailed(t *testing.T) { // Arrange - checks := map[string]mockErrorsArangoClientBackup{ - "get": { - getError: getError, - }, - "upload": { - uploadError: uploadError, - }, - } + error := newTemporaryErrorf("error") + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) - for name, c := range checks { - t.Run(name, func(t *testing.T) { - // Arrange - handler, mock := newErrorsFakeHandler(c) + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) - obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) + createResponse, err := mock.Create() + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(driver.BackupMeta{ + ID: createResponse.ID, + }, nil) + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + require.Equal(t, obj.Status, newObj.Status) +} - backupMeta, err := mock.Create() - require.NoError(t, err) +func Test_State_Upload_FatalGetFailed(t *testing.T) { + // Arrange + error := newFatalErrorf("error") + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) - // Act - createArangoDeployment(t, handler, deployment) - createArangoBackup(t, handler, obj) + createResponse, err := mock.Create() + require.NoError(t, err) - require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + obj.Status.Backup = createBackupFromMeta(driver.BackupMeta{ + ID: createResponse.ID, + }, nil) - // Assert - newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed) + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) - require.Nil(t, newObj.Status.Progress) - progresses := mock.getProgressIDs() - require.Len(t, progresses, 0) + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) - require.False(t, newObj.Status.Available) + // Assert + newObj := refreshArangoBackup(t, handler, obj) + require.Equal(t, obj.Status, newObj.Status) +} - require.NotNil(t, newObj.Status.Backup) - require.Equal(t, string(backupMeta.ID), newObj.Status.Backup.ID) - require.Equal(t, backupMeta.Version, newObj.Status.Backup.Version) - }) +func Test_State_Upload_BackupMissing(t *testing.T) { + // Arrange + error := driver.ArangoError{ + Code: 404, } + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) + + createResponse, err := mock.Create() + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(driver.BackupMeta{ + ID: createResponse.ID, + }, nil) + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateDeleted, false) } -func Test_State_Upload_TemporaryFailed(t *testing.T) { +func Test_State_Upload_TemporaryUploadFailed(t *testing.T) { // Arrange - checks := map[string]mockErrorsArangoClientBackup{ - "get": { - getError: getError, + error := newTemporaryErrorf("error") + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + uploadError: error, + }) - isTemporaryError: true, - }, - "upload": { - uploadError: uploadError, + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) - isTemporaryError: true, - }, - } + createResponse, err := mock.Create() + require.NoError(t, err) - for name, c := range checks { - t.Run(name, func(t *testing.T) { - // Arrange - handler, mock := newErrorsFakeHandler(c) + obj.Status.Backup = createBackupFromMeta(driver.BackupMeta{ + ID: createResponse.ID, + }, nil) - obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) - backupMeta, err := mock.Create() - require.NoError(t, err) + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploadError, true) +} + +func Test_State_Upload_FatalUploadFailed(t *testing.T) { + // Arrange + error := newFatalErrorf("error") + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + uploadError: error, + }) - // Act - createArangoDeployment(t, handler, deployment) - createArangoBackup(t, handler, obj) + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUpload) + + createResponse, err := mock.Create() + require.NoError(t, err) - err = handler.Handle(newItemFromBackup(operation.Update, obj)) + obj.Status.Backup = createBackupFromMeta(driver.BackupMeta{ + ID: createResponse.ID, + }, nil) - // Assert - require.Error(t, err) - require.True(t, IsTemporaryError(err)) - }) - } + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploadError, true) } diff --git a/pkg/backup/handlers/arango/backup/state_uploaderror.go b/pkg/backup/handlers/arango/backup/state_uploaderror.go index cb80c621e..501fa3a9b 100644 --- a/pkg/backup/handlers/arango/backup/state_uploaderror.go +++ b/pkg/backup/handlers/arango/backup/state_uploaderror.go @@ -32,28 +32,14 @@ const ( uploadDelay = time.Minute ) -func stateUploadErrorHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { - // After upload removal go into Ready status - if backup.Spec.Upload == nil { - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: backup.Status.Backup.DeepCopy(), - }, nil +func stateUploadErrorHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + if backup.Spec.Upload == nil || backup.Status.Time.Time.Add(uploadDelay).Before(time.Now()) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, ""), + cleanStatusJob(), + updateStatusAvailable(true)) } - // Start again upload - if backup.Status.Time.Time.Add(uploadDelay).Before(time.Now()) { - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: backup.Status.Backup.DeepCopy(), - }, nil - } - - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: backup.Status.ArangoBackupState, - Backup: backup.Status.Backup.DeepCopy(), - }, nil + return wrapUpdateStatus(backup, + updateStatusAvailable(true)) } diff --git a/pkg/backup/handlers/arango/backup/state_uploaderror_test.go b/pkg/backup/handlers/arango/backup/state_uploaderror_test.go index 118ebe36d..4a27cd6b5 100644 --- a/pkg/backup/handlers/arango/backup/state_uploaderror_test.go +++ b/pkg/backup/handlers/arango/backup/state_uploaderror_test.go @@ -103,9 +103,8 @@ func Test_State_UploadError_Wait(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateUploadError) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploadError, true) - require.True(t, newObj.Status.Available) require.Equal(t, "message", newObj.Status.Message) require.NotNil(t, newObj.Status.Backup) @@ -137,9 +136,7 @@ func Test_State_UploadError_BackToReady(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateReady) - - require.True(t, newObj.Status.Available) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) require.NotNil(t, newObj.Status.Backup) require.Equal(t, obj.Status.Backup, newObj.Status.Backup) diff --git a/pkg/backup/handlers/arango/backup/state_uploading.go b/pkg/backup/handlers/arango/backup/state_uploading.go index 9cace76af..e1338d73d 100644 --- a/pkg/backup/handlers/arango/backup/state_uploading.go +++ b/pkg/backup/handlers/arango/backup/state_uploading.go @@ -30,61 +30,59 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) -func stateUploadingHandler(h *handler, backup *backupApi.ArangoBackup) (backupApi.ArangoBackupStatus, error) { +func stateUploadingHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { deployment, err := h.getArangoDeploymentObject(backup) if err != nil { - return createFailedState(err, backup.Status), nil + return nil, err } client, err := h.arangoClientFactory(deployment, backup) if err != nil { - return backupApi.ArangoBackupStatus{}, NewTemporaryError("unable to create client: %s", err.Error()) + return nil, newTemporaryError(err) } if backup.Status.Backup == nil { - return createFailedState(fmt.Errorf("backup details are missing"), backup.Status), nil + return nil, newFatalErrorf("missing field .status.backup") } if backup.Status.Progress == nil { - return createFailedState(fmt.Errorf("backup progress details are missing"), backup.Status), nil + return nil, newFatalErrorf("missing field .status.progress") } details, err := client.Progress(driver.BackupTransferJobID(backup.Status.Progress.JobID)) if err != nil { if driver.IsNotFound(err) { - return switchTemporaryError(err, backup.Status) + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUploadError, + "job with id %s does not exist anymore", backup.Status.Progress.JobID), + cleanStatusJob(), + updateStatusAvailable(true), + ) } - return backup.Status, nil + return nil, newTemporaryError(err) } if details.Failed { - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateUploadError, - fmt.Sprintf("Upload failed with error: %s", details.FailMessage), nil), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUploadError, + "Upload failed with error: %s", details.FailMessage), + cleanStatusJob(), + updateStatusAvailable(true), + ) } if details.Completed { - newDetails := backup.Status.Backup.DeepCopy() - - newDetails.Uploaded = util.NewBool(true) - - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateReady, "", nil), - Backup: newDetails, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, ""), + cleanStatusJob(), + updateStatusBackupUpload(util.NewBool(true)), + updateStatusAvailable(true), + ) } - return backupApi.ArangoBackupStatus{ - Available: true, - ArangoBackupState: newState(backupApi.ArangoBackupStateUploading, "", - &backupApi.ArangoBackupProgress{ - JobID: backup.Status.Progress.JobID, - Progress: fmt.Sprintf("%d%%", details.Progress), - }), - Backup: backup.Status.Backup, - }, nil + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUploading,""), + updateStatusAvailable(true), + updateStatusJob(backup.Status.Progress.JobID, fmt.Sprintf("%d%%", details.Progress)), + ) } diff --git a/pkg/backup/handlers/arango/backup/state_uploading_test.go b/pkg/backup/handlers/arango/backup/state_uploading_test.go index 26a4fd454..7dee70291 100644 --- a/pkg/backup/handlers/arango/backup/state_uploading_test.go +++ b/pkg/backup/handlers/arango/backup/state_uploading_test.go @@ -24,13 +24,13 @@ package backup import ( "fmt" + "github.com/arangodb/go-driver" "testing" "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" "github.com/stretchr/testify/require" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_State_Uploading_Common(t *testing.T) { @@ -45,17 +45,16 @@ func Test_State_Uploading_Success(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateUploading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Upload(backupMeta.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Status.Progress = &backupApi.ArangoBackupProgress{ JobID: string(progress), @@ -70,16 +69,14 @@ func Test_State_Uploading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUploading, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploading, true) require.Equal(t, fmt.Sprintf("%d%%", 0), newObj.Status.Progress.Progress) require.Equal(t, obj.Status.Progress.JobID, newObj.Status.Progress.JobID) - - require.True(t, newObj.Status.Available) }) t.Run("Restore percent after update", func(t *testing.T) { p := 55 - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Progress: p, } @@ -87,15 +84,13 @@ func Test_State_Uploading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUploading, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploading, true) require.Equal(t, fmt.Sprintf("%d%%", p), newObj.Status.Progress.Progress) require.Equal(t, string(progress), newObj.Status.Progress.JobID) - - require.True(t, newObj.Status.Available) }) t.Run("Finished", func(t *testing.T) { - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Completed: true, } @@ -103,10 +98,9 @@ func Test_State_Uploading_Success(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateReady, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) require.Nil(t, newObj.Status.Progress) - require.True(t, newObj.Status.Available) require.NotNil(t, newObj.Status.Backup.Uploaded) require.True(t, *newObj.Status.Backup.Uploaded) }) @@ -118,23 +112,22 @@ func Test_State_Uploading_FailedUpload(t *testing.T) { obj, deployment := newObjectSet(backupApi.ArangoBackupStateUploading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) progress, err := mock.Upload(backupMeta.ID) require.NoError(t, err) errorMsg := errorString - mock.progresses[progress] = ArangoBackupProgress{ + mock.state.progresses[progress] = ArangoBackupProgress{ Failed: true, FailMessage: errorMsg, } - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) obj.Status.Progress = &backupApi.ArangoBackupProgress{ JobID: string(progress), @@ -148,35 +141,37 @@ func Test_State_Uploading_FailedUpload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUploadError, newObj.Status.State) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploadError, true) require.Equal(t, fmt.Sprintf("Upload failed with error: %s", errorMsg), newObj.Status.Message) require.Nil(t, newObj.Status.Progress) - - require.True(t, newObj.Status.Available) } -func Test_State_Uploading_FailedProgress(t *testing.T) { +func Test_StateUploading_FailedProgress(t *testing.T) { // Arrange - errorMsg := "progress error" + error := newFatalErrorf("error") handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - progressError: errorMsg, + progressError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateUploading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - progress, err := mock.Upload(backupMeta.ID) + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + progress, err := mock.Download(backupMeta.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) - obj.Status.Available = true + obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ + ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ + RepositoryURL: "S3 URL", + }, + ID: string(backupMeta.ID), + } obj.Status.Progress = &backupApi.ArangoBackupProgress{ JobID: string(progress), @@ -186,39 +181,39 @@ func Test_State_Uploading_FailedProgress(t *testing.T) { createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUploading, newObj.Status.State) - require.NotNil(t, newObj.Status.Progress) - - require.True(t, newObj.Status.Available) + require.Equal(t, obj.Status, newObj.Status) } func Test_State_Uploading_TemporaryFailedProgress(t *testing.T) { // Arrange - errorMsg := "progress error" + error := newTemporaryErrorf("error") handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ - isTemporaryError: true, - progressError: errorMsg, + progressError: error, }) obj, deployment := newObjectSet(backupApi.ArangoBackupStateUploading) - backupMeta, err := mock.Create() + createResponse, err := mock.Create() require.NoError(t, err) - progress, err := mock.Upload(backupMeta.ID) + backupMeta, err := mock.Get(createResponse.ID) require.NoError(t, err) - obj.Status.Backup = &backupApi.ArangoBackupDetails{ - ID: string(backupMeta.ID), - Version: backupMeta.Version, - CreationTimestamp: meta.Now(), - } + progress, err := mock.Download(backupMeta.ID) + require.NoError(t, err) - obj.Status.Available = true + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ + ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ + RepositoryURL: "S3 URL", + }, + ID: string(backupMeta.ID), + } obj.Status.Progress = &backupApi.ArangoBackupProgress{ JobID: string(progress), @@ -228,12 +223,55 @@ func Test_State_Uploading_TemporaryFailedProgress(t *testing.T) { createArangoDeployment(t, handler, deployment) createArangoBackup(t, handler, obj) - err = handler.Handle(newItemFromBackup(operation.Update, obj)) + require.EqualError(t, handler.Handle(newItemFromBackup(operation.Update, obj)), error.Error()) // Assert newObj := refreshArangoBackup(t, handler, obj) - require.Equal(t, backupApi.ArangoBackupStateUploading, newObj.Status.State) - require.NotNil(t, newObj.Status.Progress) + require.Equal(t, obj.Status, newObj.Status) +} - require.True(t, newObj.Status.Available) +func Test_State_Uploading_NotFoundProgress(t *testing.T) { + // Arrange + error := driver.ArangoError{ + Code: 404, + } + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + progressError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUploading) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + progress, err := mock.Download(backupMeta.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + obj.Spec.Download = &backupApi.ArangoBackupSpecDownload{ + ArangoBackupSpecOperation: backupApi.ArangoBackupSpecOperation{ + RepositoryURL: "S3 URL", + }, + ID: string(backupMeta.ID), + } + + obj.Status.Progress = &backupApi.ArangoBackupProgress{ + JobID: string(progress), + } + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUploadError, true) + require.Equal(t, fmt.Sprintf("job with id %s does not exist anymore", progress), newObj.Status.Message) + require.Nil(t, newObj.Status.Progress) } diff --git a/pkg/backup/handlers/arango/backup/status.go b/pkg/backup/handlers/arango/backup/status.go new file mode 100644 index 000000000..51ca327a1 --- /dev/null +++ b/pkg/backup/handlers/arango/backup/status.go @@ -0,0 +1,148 @@ +// +// DISCLAIMER +// +// Copyright 2018 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package backup + +import ( + "fmt" + "github.com/arangodb/go-driver" + backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" + "github.com/arangodb/kube-arangodb/pkg/backup/state" + "github.com/arangodb/kube-arangodb/pkg/util" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type updateStatusFunc func(status *backupApi.ArangoBackupStatus) + +func wrapUpdateStatus(backup *backupApi.ArangoBackup, update ... updateStatusFunc) (*backupApi.ArangoBackupStatus, error) { + return updateStatus(backup, update...), nil +} + +func updateStatus(backup *backupApi.ArangoBackup, update ... updateStatusFunc) *backupApi.ArangoBackupStatus { + s := backup.Status.DeepCopy() + + for _, u := range update { + u(s) + } + + return s +} + +func updateStatusState(state state.State, template string, a ... interface{}) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + if status.State != state { + status.Time = v1.Now() + } + status.State = state + status.Message = fmt.Sprintf(template, a...) + } +} + +func updateStatusAvailable(available bool) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + status.Available = available + } +} + +func updateStatusJob(id, progress string) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + status.Progress = &backupApi.ArangoBackupProgress{ + JobID: id, + Progress: progress, + } + } +} + +func updateStatusBackupUpload(uploaded *bool) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + if status.Backup != nil { + status.Backup.Uploaded = uploaded + } + } +} + +func updateStatusBackupImported(imported *bool) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + if status.Backup != nil { + status.Backup.Imported = imported + } + } +} + +func updateStatusBackupDownload(downloaded *bool) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + if status.Backup != nil { + status.Backup.Downloaded = downloaded + } + } +} + +func updateStatusBackup(backupMeta driver.BackupMeta) updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + status.Backup = createBackupFromMeta(backupMeta, status.Backup) + } +} + +func cleanStatusJob() updateStatusFunc { + return func(status *backupApi.ArangoBackupStatus) { + status.Progress = nil + } +} + +func setFailedState(backup *backupApi.ArangoBackup, err error) (*backupApi.ArangoBackupStatus, error) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateFailed, createStateMessage(backup.Status.State, backupApi.ArangoBackupStateFailed, err.Error())), + updateStatusAvailable(false)) +} + +func createStateMessage(from, to state.State, message string) string { + return fmt.Sprintf("Transiting from %s to %s: %s", from, to, message) +} + +func switchTemporaryError(backup *backupApi.ArangoBackup, err error) (*backupApi.ArangoBackupStatus, error) { + if _, ok := err.(temporaryError); ok { + return nil, err + } + + return setFailedState(backup, err) +} + +func createBackupFromMeta(backupMeta driver.BackupMeta, old *backupApi.ArangoBackupDetails) *backupApi.ArangoBackupDetails { + var obj *backupApi.ArangoBackupDetails + + if old == nil { + obj = &backupApi.ArangoBackupDetails{} + } else { + obj = old.DeepCopy() + } + + obj.PotentiallyInconsistent = util.NewBool(backupMeta.PotentiallyInconsistent) + obj.SizeInBytes = backupMeta.SizeInBytes + obj.CreationTimestamp = v1.Time{ + Time: backupMeta.DateTime, + } + obj.NumberOfDBServers = backupMeta.NumberOfDBServers + obj.Version = backupMeta.Version + obj.ID = string(backupMeta.ID) + + return obj +} \ No newline at end of file diff --git a/pkg/backup/handlers/arango/backup/util.go b/pkg/backup/handlers/arango/backup/util.go index 107de594d..f09ebd4b1 100644 --- a/pkg/backup/handlers/arango/backup/util.go +++ b/pkg/backup/handlers/arango/backup/util.go @@ -23,14 +23,8 @@ package backup import ( - "fmt" - "reflect" "strings" - "github.com/arangodb/go-driver" - "github.com/arangodb/kube-arangodb/pkg/backup/utils" - "github.com/rs/zerolog/log" - clientBackup "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/typed/backup/v1alpha" meta "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -50,49 +44,6 @@ var ( } ) -func switchTemporaryError(err error, status backupApi.ArangoBackupStatus) (backupApi.ArangoBackupStatus, error) { - if checkTemporaryError(err) { - return backupApi.ArangoBackupStatus{}, err - } - - return createFailedState(err, status), nil -} - -func createFailMessage(state state.State, message string) string { - return fmt.Sprintf("Failed State %s: %s", state, message) -} - -func createFailedState(err error, status backupApi.ArangoBackupStatus) backupApi.ArangoBackupStatus { - e := log.Error().Err(err).Str("type", reflect.TypeOf(err).String()) - if c, ok := err.(utils.Causer); ok { - e = e.AnErr("caused", c.Cause()).Str("causedType", reflect.TypeOf(c.Cause()).String()).Str("causedError", fmt.Sprintf("%v", c.Cause())) - - if a, ok := c.Cause().(driver.ArangoError); ok { - e = e.Str("aMsg", a.ErrorMessage).Int("aCode", a.Code).Int("aNum", a.ErrorNum).Str("aMsg", a.ErrorMessage).Bool("aTemp", a.Temporary()) - } - } - e.Msgf("Error %v", err) - - newStatus := status.DeepCopy() - - newStatus.ArangoBackupState = newState(backupApi.ArangoBackupStateFailed, createFailMessage(status.State, err.Error()), nil) - - newStatus.Available = false - - return *newStatus -} - -func newState(state state.State, message string, progress *backupApi.ArangoBackupProgress) backupApi.ArangoBackupState { - return backupApi.ArangoBackupState{ - State: state, - Time: meta.Now(), - - Message: message, - - Progress: progress, - } -} - func inProgress(backup *backupApi.ArangoBackup) bool { for _, state := range progressStates { if state == backup.Status.State { @@ -107,7 +58,7 @@ func isBackupRunning(backup *backupApi.ArangoBackup, client clientBackup.ArangoB backups, err := client.List(meta.ListOptions{}) if err != nil { - return false, err + return false, newTemporaryError(err) } for _, existingBackup := range backups.Items { diff --git a/tests/backup_test.go b/tests/backup_test.go index 46bdf8383..52fd1e99e 100644 --- a/tests/backup_test.go +++ b/tests/backup_test.go @@ -25,6 +25,7 @@ package tests import ( "context" "fmt" + "github.com/arangodb/kube-arangodb/pkg/backup/utils" "os" "strings" "testing" @@ -199,9 +200,18 @@ func newBackupPolicy(name, schedule string, labels map[string]string, options *E } func skipIfBackupUnavailable(t *testing.T, client driver.Client) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - if _, err := client.Backup().List(ctx, nil); err != nil { + err := utils.Retry(10, time.Second, func() error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + if _, err := client.Backup().List(ctx, nil); err != nil { + t.Logf("Backup API not yet ready: %s", err.Error()) + return err + } + + return nil + }) + + if err != nil { t.Skipf("Backup API not available: %s", err.Error()) } } @@ -295,6 +305,15 @@ func timeoutWaitForBackups(t *testing.T, backupClient backupClient.ArangoBackupI } } +func compareBackup(t *testing.T, meta driver.BackupMeta, backup *backupApi.ArangoBackup) { + require.NotNil(t, backup.Status.Backup) + require.Equal(t, meta.Version, backup.Status.Backup.Version) + require.True(t, meta.SizeInBytes > 0) + require.True(t, meta.NumberOfDBServers == 3) + require.True(t, meta.SizeInBytes == backup.Status.Backup.SizeInBytes) + require.True(t, meta.NumberOfDBServers == backup.Status.Backup.NumberOfDBServers) +} + func TestBackupCluster(t *testing.T) { longOrSkip(t) c := client.MustNewInCluster() @@ -401,7 +420,7 @@ func TestBackupCluster(t *testing.T) { found, meta, err := statBackupMeta(databaseClient, driver.BackupID(backupID)) require.NoError(t, err, "Backup test failed: %s", err) require.True(t, found) - require.Equal(t, meta.Version, backup.Status.Backup.Version) + compareBackup(t, meta, backup) }) t.Run("create-upload backup", func(t *testing.T) { @@ -418,7 +437,7 @@ func TestBackupCluster(t *testing.T) { found, meta, err := statBackupMeta(databaseClient, driver.BackupID(backupID)) require.NoError(t, err, "Backup test failed: %s", err) require.True(t, found) - require.Equal(t, meta.Version, backup.Status.Backup.Version) + compareBackup(t, meta, backup) require.Nil(t, backup.Status.Backup.Uploaded) require.Nil(t, backup.Status.Backup.Downloaded) @@ -439,7 +458,7 @@ func TestBackupCluster(t *testing.T) { found, meta, err = statBackupMeta(databaseClient, driver.BackupID(backupID)) require.NoError(t, err, "Backup test failed: %s", err) require.True(t, found) - require.Equal(t, meta.Version, backup.Status.Backup.Version) + compareBackup(t, meta, backup) require.NotNil(t, backup.Status.Backup.Uploaded, "Upload flag is nil") require.Nil(t, backup.Status.Backup.Downloaded) }) @@ -452,7 +471,7 @@ func TestBackupCluster(t *testing.T) { found, meta, err := statBackupMeta(databaseClient, id) require.NoError(t, err, "Backup test failed: %s", err) require.True(t, found) - require.Equal(t, meta.Version, backup.Status.Backup.Version) + compareBackup(t, meta, backup) // now remove the backup backupClient.Delete(name, &metav1.DeleteOptions{}) @@ -482,7 +501,7 @@ func TestBackupCluster(t *testing.T) { // create a local backup manually id, _, err := databaseClient.Backup().Create(nil, nil) require.NoError(t, err, "Creating backup failed: %s", err) - found, _, err := statBackupMeta(databaseClient, driver.BackupID(id)) + found, meta, err := statBackupMeta(databaseClient, driver.BackupID(id)) require.NoError(t, err, "Backup test failed: %s", err) require.True(t, found) @@ -513,7 +532,7 @@ func TestBackupCluster(t *testing.T) { backup, err = waitUntilBackup(deploymentClient, backup.GetName(), ns, backupIsAvailable) require.NoError(t, err, "backup did not become available: %s", err) require.Equal(t, backupApi.ArangoBackupStateReady, backup.Status.State) - require.NotNil(t, backup.Status.Backup) + compareBackup(t, meta, backup) require.NotNil(t, backup.Status.Backup.Imported) require.True(t, *backup.Status.Backup.Imported) }) From 1ed279892cf727119f00f340361a0b66d8156739 Mon Sep 17 00:00:00 2001 From: ajanikow Date: Tue, 1 Oct 2019 23:34:47 +0000 Subject: [PATCH 2/6] Fix size --- tests/backup_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/backup_test.go b/tests/backup_test.go index 52fd1e99e..312ef56db 100644 --- a/tests/backup_test.go +++ b/tests/backup_test.go @@ -309,7 +309,7 @@ func compareBackup(t *testing.T, meta driver.BackupMeta, backup *backupApi.Arang require.NotNil(t, backup.Status.Backup) require.Equal(t, meta.Version, backup.Status.Backup.Version) require.True(t, meta.SizeInBytes > 0) - require.True(t, meta.NumberOfDBServers == 3) + require.True(t, meta.NumberOfDBServers == 2) require.True(t, meta.SizeInBytes == backup.Status.Backup.SizeInBytes) require.True(t, meta.NumberOfDBServers == backup.Status.Backup.NumberOfDBServers) } From dbb10d1da899ca39740751788175e0e1683f040d Mon Sep 17 00:00:00 2001 From: ajanikow Date: Wed, 2 Oct 2019 00:14:30 +0000 Subject: [PATCH 3/6] Allow transition --- pkg/apis/backup/v1alpha/backup_state.go | 2 +- .../handlers/arango/backup/state_deleted.go | 25 ++++++++++++++++- .../arango/backup/state_deleted_test.go | 27 +++++++++++++++++++ .../arango/backup/state_ready_test.go | 3 +++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/pkg/apis/backup/v1alpha/backup_state.go b/pkg/apis/backup/v1alpha/backup_state.go index 22057dc0b..7d3776f4f 100644 --- a/pkg/apis/backup/v1alpha/backup_state.go +++ b/pkg/apis/backup/v1alpha/backup_state.go @@ -56,7 +56,7 @@ var ArangoBackupStateMap = state.Map{ ArangoBackupStateUploading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateUploadError}, ArangoBackupStateUploadError: {ArangoBackupStateFailed, ArangoBackupStateReady}, ArangoBackupStateReady: {ArangoBackupStateDeleted, ArangoBackupStateFailed, ArangoBackupStateUpload}, - ArangoBackupStateDeleted: {ArangoBackupStateFailed}, + ArangoBackupStateDeleted: {ArangoBackupStateFailed, ArangoBackupStateReady}, ArangoBackupStateFailed: {ArangoBackupStatePending}, ArangoBackupStateUnavailable: {ArangoBackupStateReady}, } diff --git a/pkg/backup/handlers/arango/backup/state_deleted.go b/pkg/backup/handlers/arango/backup/state_deleted.go index 83f5ed1fc..f7c544697 100644 --- a/pkg/backup/handlers/arango/backup/state_deleted.go +++ b/pkg/backup/handlers/arango/backup/state_deleted.go @@ -23,9 +23,32 @@ package backup import ( + "github.com/arangodb/go-driver" backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" ) func stateDeletedHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { - return wrapUpdateStatus(backup) + deployment, err := h.getArangoDeploymentObject(backup) + if err != nil { + return nil, err + } + + client, err := h.arangoClientFactory(deployment, backup) + if err != nil { + return nil, newTemporaryError(err) + } + + if backup.Status.Backup != nil { + backupMeta, err := client.Get(driver.BackupID(backup.Status.Backup.ID)) + if err == nil { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateReady, ""), + updateStatusAvailable(true), + updateStatusBackup(backupMeta), + ) + } + } + + return wrapUpdateStatus(backup, + updateStatusAvailable(false)) } diff --git a/pkg/backup/handlers/arango/backup/state_deleted_test.go b/pkg/backup/handlers/arango/backup/state_deleted_test.go index aeb181b84..afbd10eeb 100644 --- a/pkg/backup/handlers/arango/backup/state_deleted_test.go +++ b/pkg/backup/handlers/arango/backup/state_deleted_test.go @@ -46,4 +46,31 @@ func Test_State_Deleted(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) require.Equal(t, newObj.Status, obj.Status) + checkBackup(t, newObj, backupApi.ArangoBackupStateDeleted, false) +} + +func Test_State_Deleted_Recover(t *testing.T) { + // Arrange + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateDeleted) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) } diff --git a/pkg/backup/handlers/arango/backup/state_ready_test.go b/pkg/backup/handlers/arango/backup/state_ready_test.go index 50e15a3fd..ed9df944a 100644 --- a/pkg/backup/handlers/arango/backup/state_ready_test.go +++ b/pkg/backup/handlers/arango/backup/state_ready_test.go @@ -210,6 +210,7 @@ func Test_State_Ready_Upload(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) checkBackup(t, newObj, backupApi.ArangoBackupStateUpload, true) + compareBackupMeta(t, backupMeta, newObj) } func Test_State_Ready_DownloadDoNothing(t *testing.T) { @@ -243,6 +244,7 @@ func Test_State_Ready_DownloadDoNothing(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) require.NotNil(t, newObj.Status.Backup.Downloaded) require.True(t, *newObj.Status.Backup.Downloaded) } @@ -308,6 +310,7 @@ func Test_State_Ready_DoNotReUploadBackup(t *testing.T) { // Assert newObj := refreshArangoBackup(t, handler, obj) checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) require.NotNil(t, newObj.Status.Backup.Uploaded) require.True(t, *newObj.Status.Backup.Uploaded) } From fd1c71dc483ad6859c88ea9ac13280fee1249975 Mon Sep 17 00:00:00 2001 From: ajanikow Date: Wed, 2 Oct 2019 10:41:47 +0000 Subject: [PATCH 4/6] Add available flag --- go.mod | 4 +- go.sum | 2 + pkg/apis/backup/v1alpha/backup_state.go | 4 +- .../arango/backup/arango_client_mock_test.go | 1 + pkg/backup/handlers/arango/backup/state.go | 1 + .../handlers/arango/backup/state_ready.go | 8 + .../arango/backup/state_ready_test.go | 30 +++ .../arango/backup/state_unavailable.go | 72 ++++++ .../arango/backup/state_unavailable_test.go | 215 ++++++++++++++++++ 9 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 pkg/backup/handlers/arango/backup/state_unavailable.go create mode 100644 pkg/backup/handlers/arango/backup/state_unavailable_test.go diff --git a/go.mod b/go.mod index b1adf721a..438ef107c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/aktau/github-release v0.7.2 // indirect github.com/arangodb-helper/go-certificates v0.0.0-20180821055445-9fca24fc2680 github.com/arangodb/arangosync-client v0.6.3 - github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53 + github.com/arangodb/go-driver v0.0.0-20191002081852-02f5e9c3a694 github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/bugagazavr/go-gitlab-client v0.0.0-20150830002541-e5999f934dc4 // indirect @@ -62,7 +62,7 @@ require ( github.com/juju/errgo v0.0.0-20140925100237-08cceb5d0b53 // indirect github.com/julienschmidt/httprouter v1.2.0 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 // indirect - github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a + github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect diff --git a/go.sum b/go.sum index 2020ac96c..ce72a31c9 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ github.com/arangodb/go-driver v0.0.0-20190917135832-4ec315b17bf0 h1:OZAHgwP/X6eJ github.com/arangodb/go-driver v0.0.0-20190917135832-4ec315b17bf0/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53 h1:gKOy8ZeLmAMWoCfZtXAwTpl9S3UxHjv8l4T96BvuXoQ= github.com/arangodb/go-driver v0.0.0-20191001090752-e07a8fe34b53/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= +github.com/arangodb/go-driver v0.0.0-20191002081852-02f5e9c3a694 h1:Cn68e0b50vTJqku80erSNWddXixyyvzVOy7cgb4MKYw= +github.com/arangodb/go-driver v0.0.0-20191002081852-02f5e9c3a694/go.mod h1:5nMxlcbN6PHA05uGUw2EH6PIQHvdRVTrg/S/GM8jG4w= github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21 h1:+W7D5ttxi/Ygh/39vialtypE23p9KI7P0J2qtoqUV4w= github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21/go.mod h1:RkPIG6JJ2pcJUoymc18NxAJGraZd+iAEVnOTDjZey/w= github.com/arangodb/go-velocypack v0.0.0-20190129082528-7896a965b4ad h1:Ah+VRYUWLuqgbfnDyuC8IrIe8XFzpt9tBVVnPGFNTJ8= diff --git a/pkg/apis/backup/v1alpha/backup_state.go b/pkg/apis/backup/v1alpha/backup_state.go index 7d3776f4f..2d5c609cd 100644 --- a/pkg/apis/backup/v1alpha/backup_state.go +++ b/pkg/apis/backup/v1alpha/backup_state.go @@ -55,10 +55,10 @@ var ArangoBackupStateMap = state.Map{ ArangoBackupStateUpload: {ArangoBackupStateUploading, ArangoBackupStateFailed, ArangoBackupStateDeleted, ArangoBackupStateUploadError}, ArangoBackupStateUploading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateUploadError}, ArangoBackupStateUploadError: {ArangoBackupStateFailed, ArangoBackupStateReady}, - ArangoBackupStateReady: {ArangoBackupStateDeleted, ArangoBackupStateFailed, ArangoBackupStateUpload}, + ArangoBackupStateReady: {ArangoBackupStateDeleted, ArangoBackupStateFailed, ArangoBackupStateUpload, ArangoBackupStateUnavailable}, ArangoBackupStateDeleted: {ArangoBackupStateFailed, ArangoBackupStateReady}, ArangoBackupStateFailed: {ArangoBackupStatePending}, - ArangoBackupStateUnavailable: {ArangoBackupStateReady}, + ArangoBackupStateUnavailable: {ArangoBackupStateReady, ArangoBackupStateDeleted, ArangoBackupStateFailed}, } type ArangoBackupState struct { diff --git a/pkg/backup/handlers/arango/backup/arango_client_mock_test.go b/pkg/backup/handlers/arango/backup/arango_client_mock_test.go index bec4df82e..5f7526017 100644 --- a/pkg/backup/handlers/arango/backup/arango_client_mock_test.go +++ b/pkg/backup/handlers/arango/backup/arango_client_mock_test.go @@ -217,6 +217,7 @@ func (m *mockArangoClientBackup) Create() (ArangoBackupCreateResponse, error) { SizeInBytes: rand.Uint64(), PotentiallyInconsistent: inconsistent, NumberOfFiles: uint(rand.Uint32()), + Available: true, } m.state.backups[id] = meta diff --git a/pkg/backup/handlers/arango/backup/state.go b/pkg/backup/handlers/arango/backup/state.go index f1d0e0af4..c13c3a516 100644 --- a/pkg/backup/handlers/arango/backup/state.go +++ b/pkg/backup/handlers/arango/backup/state.go @@ -44,5 +44,6 @@ var ( backupApi.ArangoBackupStateReady: stateReadyHandler, backupApi.ArangoBackupStateDeleted: stateDeletedHandler, backupApi.ArangoBackupStateFailed: stateFailedHandler, + backupApi.ArangoBackupStateUnavailable: stateUnavailableHandler, } ) diff --git a/pkg/backup/handlers/arango/backup/state_ready.go b/pkg/backup/handlers/arango/backup/state_ready.go index 0711d558a..0cb6c52f3 100644 --- a/pkg/backup/handlers/arango/backup/state_ready.go +++ b/pkg/backup/handlers/arango/backup/state_ready.go @@ -56,6 +56,14 @@ func stateReadyHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.A ) } + if backupMeta.Available == false { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUnavailable, ""), + updateStatusBackup(backupMeta), + updateStatusAvailable(false), + ) + } + // Check if upload flag was specified later in runtime if backup.Spec.Upload != nil && (backup.Status.Backup.Uploaded == nil || (backup.Status.Backup.Uploaded != nil && !*backup.Status.Backup.Uploaded)) { diff --git a/pkg/backup/handlers/arango/backup/state_ready_test.go b/pkg/backup/handlers/arango/backup/state_ready_test.go index ed9df944a..73cf441d2 100644 --- a/pkg/backup/handlers/arango/backup/state_ready_test.go +++ b/pkg/backup/handlers/arango/backup/state_ready_test.go @@ -66,6 +66,36 @@ func Test_State_Ready_Success(t *testing.T) { compareBackupMeta(t, backupMeta, newObj) } +func Test_State_Ready_Unavailable(t *testing.T) { + // Arrange + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateReady) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + backupMeta.Available = false + + mock.state.backups[createResponse.ID] = backupMeta + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) + compareBackupMeta(t, backupMeta, newObj) +} + func Test_State_Ready_Success_Update(t *testing.T) { // Arrange handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) diff --git a/pkg/backup/handlers/arango/backup/state_unavailable.go b/pkg/backup/handlers/arango/backup/state_unavailable.go new file mode 100644 index 000000000..e7656bb82 --- /dev/null +++ b/pkg/backup/handlers/arango/backup/state_unavailable.go @@ -0,0 +1,72 @@ +// +// DISCLAIMER +// +// Copyright 2018 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package backup + +import ( + "github.com/arangodb/go-driver" + backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" +) + +func stateUnavailableHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) { + deployment, err := h.getArangoDeploymentObject(backup) + if err != nil { + return nil, err + } + + client, err := h.arangoClientFactory(deployment, backup) + if err != nil { + return nil, newTemporaryError(err) + } + + if backup.Status.Backup == nil { + return nil, newFatalErrorf("missing field .status.backup") + } + + backupMeta, err := client.Get(driver.BackupID(backup.Status.Backup.ID)) + if err != nil { + if driver.IsNotFound(err) { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateDeleted, ""), + updateStatusAvailable(false), + ) + } + + return wrapUpdateStatus(backup, + updateStatusAvailable(false), + ) + } + + if backupMeta.Available == false { + return wrapUpdateStatus(backup, + updateStatusState(backupApi.ArangoBackupStateUnavailable, ""), + updateStatusBackup(backupMeta), + updateStatusAvailable(false), + ) + } + + return wrapUpdateStatus(backup, + updateStatusBackup(backupMeta), + updateStatusState(backupApi.ArangoBackupStateReady, ""), + updateStatusAvailable(true), + ) +} diff --git a/pkg/backup/handlers/arango/backup/state_unavailable_test.go b/pkg/backup/handlers/arango/backup/state_unavailable_test.go new file mode 100644 index 000000000..b076a9f37 --- /dev/null +++ b/pkg/backup/handlers/arango/backup/state_unavailable_test.go @@ -0,0 +1,215 @@ +// +// DISCLAIMER +// +// Copyright 2018 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package backup + +import ( + "github.com/arangodb/go-driver" + "github.com/arangodb/kube-arangodb/pkg/backup/operator/operation" + "github.com/stretchr/testify/require" + "testing" + + backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1alpha" +) + +func Test_State_Unavailable_Common(t *testing.T) { + wrapperUndefinedDeployment(t, backupApi.ArangoBackupStateUnavailable) + wrapperConnectionIssues(t, backupApi.ArangoBackupStateUnavailable) +} + +func Test_State_Unavailable_Success(t *testing.T) { + // Arrange + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true) + compareBackupMeta(t, backupMeta, newObj) +} + +func Test_State_Unavailable_Keep(t *testing.T) { + // Arrange + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + backupMeta.Available = false + + mock.state.backups[createResponse.ID] = backupMeta + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) + compareBackupMeta(t, backupMeta, newObj) +} + +func Test_State_Unavailable_Update(t *testing.T) { + // Arrange + handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{}) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + createResponse, err := mock.Create() + require.NoError(t, err) + + backupMeta, err := mock.Get(createResponse.ID) + require.NoError(t, err) + + obj.Status.Backup = createBackupFromMeta(backupMeta, nil) + + backupMeta.Available = false + + mock.state.backups[createResponse.ID] = backupMeta + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + t.Run("First iteration", func(t *testing.T) { + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) + compareBackupMeta(t, backupMeta, newObj) + }) + + t.Run("Second iteration", func(t *testing.T) { + backupMeta.SizeInBytes = 123 + mock.state.backups[backupMeta.ID] = backupMeta + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) + compareBackupMeta(t, backupMeta, newObj) + require.Equal(t, uint64(123), newObj.Status.Backup.SizeInBytes) + }) + + t.Run("Do nothing", func(t *testing.T) { + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) + compareBackupMeta(t, backupMeta, newObj) + }) +} + +func Test_State_Unavailable_TemporaryGetFailed(t *testing.T) { + // Arrange + error := newTemporaryErrorf("error") + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + obj.Status.Backup = &backupApi.ArangoBackupDetails{} + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) +} + +func Test_State_Unavailable_FatalGetFailed(t *testing.T) { + // Arrange + error := newFatalErrorf("error") + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + obj.Status.Backup = &backupApi.ArangoBackupDetails{} + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateUnavailable, false) +} + +func Test_State_Unavailable_MissingBackup(t *testing.T) { + // Arrange + error := driver.ArangoError{ + Code: 404, + } + handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{ + getError: error, + }) + + obj, deployment := newObjectSet(backupApi.ArangoBackupStateUnavailable) + + obj.Status.Backup = &backupApi.ArangoBackupDetails{} + + // Act + createArangoDeployment(t, handler, deployment) + createArangoBackup(t, handler, obj) + + require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj))) + + // Assert + newObj := refreshArangoBackup(t, handler, obj) + checkBackup(t, newObj, backupApi.ArangoBackupStateDeleted, false) +} From b598b107d07fcd67f038fc67de4fb72ee51cfbe8 Mon Sep 17 00:00:00 2001 From: ajanikow Date: Wed, 2 Oct 2019 13:52:32 +0000 Subject: [PATCH 5/6] Update go-driver --- dashboard/assets.go | 148 +++++++++--------- .../Deployment/Kubernetes/BackupResource.md | 1 + go.mod | 2 +- go.sum | 2 + manifests/arango-backup.yaml | 15 +- manifests/arango-crd.yaml | 89 ++++++----- 6 files changed, 139 insertions(+), 118 deletions(-) diff --git a/dashboard/assets.go b/dashboard/assets.go index f37a89eeb..18c9d5697 100644 --- a/dashboard/assets.go +++ b/dashboard/assets.go @@ -6,106 +6,106 @@ import ( "github.com/jessevdk/go-assets" ) -var _Assetsd8c43b79de2ab8f2e042af7e7df4e732895e225d = "{\"version\":3,\"sources\":[\"util/Storage.js\",\"api/api.js\",\"util/Loading.js\",\"auth/Login.js\",\"auth/LogoutContext.js\",\"auth/Auth.js\",\"style/style.js\",\"util/CommandInstruction.js\",\"deployment/MemberList.js\",\"deployment/DeploymentDetails.js\",\"deployment/DeploymentList.js\",\"deployment/DeploymentOperator.js\",\"replication/DeploymentReplicationDetails.js\",\"replication/DeploymentReplicationList.js\",\"replication/DeploymentReplicationOperator.js\",\"NoOperator.js\",\"storage/VolumeList.js\",\"storage/StorageRow.js\",\"storage/StorageList.js\",\"storage/StorageOperator.js\",\"App.js\",\"registerServiceWorker.js\",\"index.js\"],\"names\":[\"PREFIX\",\"getSessionItem\",\"key\",\"item\",\"sessionStorage\",\"getItem\",\"concat\",\"JSON\",\"parse\",\"e\",\"setSessionItem\",\"value\",\"setItem\",\"stringify\",\"isUnauthorized\",\"status\",\"api\",\"token\",\"decodeResults\",\"_decodeResults\",\"Object\",\"asyncToGenerator\",\"regenerator_default\",\"a\",\"mark\",\"_callee\",\"result\",\"decoded\",\"message\",\"wrap\",\"_context\",\"prev\",\"next\",\"json\",\"sent\",\"error\",\"assign\",\"Error\",\"abrupt\",\"stop\",\"_x\",\"apply\",\"this\",\"arguments\",\"get\",\"_get\",\"_callee2\",\"localURL\",\"headers\",\"_context2\",\"Accept\",\"fetch\",\"_x2\",\"post\",\"_post\",\"_callee3\",\"body\",\"_context3\",\"Content-Type\",\"method\",\"_x3\",\"_x4\",\"Loading\",\"_ref\",\"react_default\",\"createElement\",\"Segment\",\"Dimmer\",\"inverted\",\"active\",\"Loader\",\"style\",\"minHeight\",\"LoginView\",\"username\",\"password\",\"onUsernameChanged\",\"onPasswordChanged\",\"doLogin\",\"Container\",\"Form\",\"onSubmit\",\"Field\",\"focus\",\"onChange\",\"target\",\"type\",\"Button\",\"className\",\"css\",\"_templateObject\",\"Message\",\"content\",\"Login\",\"state\",\"handleLogin\",\"_this\",\"props\",\"_this2\",\"Modal\",\"open\",\"Header\",\"Content\",\"Login_LoginView\",\"v\",\"setState\",\"Actions\",\"color\",\"disabled\",\"onClick\",\"Icon\",\"name\",\"Component\",\"LogoutContext\",\"React\",\"createContext\",\"undefined\",\"tokenSessionKey\",\"withAuth\",\"WrappedComponent\",\"auth_LogoutContext\",\"Consumer\",\"doLogout\",\"Auth\",\"authenticated\",\"showLoading\",\"res\",\"t0\",\"handleLogout\",\"Provider\",\"util_Loading\",\"children\",\"auth_Login\",\"styled\",\"style_templateObject\",\"FieldLabel\",\"_templateObject2\",\"FieldContent\",\"_templateObject3\",\"FieldIcons\",\"_templateObject4\",\"LoaderBox\",\"_templateObject5\",\"LoaderBoxForTable\",\"_templateObject6\",\"StyledMenu\",\"Menu\",\"_templateObject7\",\"StyledContentBox\",\"_templateObject8\",\"CommandInstruction\",\"close\",\"trigger\",\"onClose\",\"onOpen\",\"title\",\"Description\",\"description\",\"clearing\",\"command\",\"lib\",\"text\",\"onCopy\",\"positive\",\"icon\",\"labelPosition\",\"MemberListView\",\"group\",\"activeMemberID\",\"members\",\"namespace\",\"List\",\"divided\",\"map\",\"MemberList_MemberView\",\"id\",\"memberInfo\",\"MemberView\",\"_ref2\",\"pvName\",\"memberOfCluster\",\"Item\",\"Accordion\",\"Title\",\"ready\",\"member_of_cluster\",\"pod_name\",\"util_CommandInstruction\",\"link\",\"createLogPodCommand\",\"createDescribePodCommand\",\"pvc_name\",\"createDescribePvcCommand\",\"pv_name\",\"podName\",\"pvcName\",\"MemberList\",\"MemberList_MemberListView\",\"MemberGroupsView\",\"memberGroups\",\"deployment_MemberList\",\"DeploymentDetails\",\"loading\",\"reloadDeployment\",\"deployment\",\"setTimeout\",\"d\",\"size\",\"inline\",\"DeploymentDetails_MemberGroupsView\",\"member_groups\",\"ReactTimeout\",\"HeaderView\",\"Table\",\"Row\",\"HeaderCell\",\"Popup\",\"DatabaseLinkView\",\"url\",\"href\",\"NoDatabaseLinkView\",\"RowView\",\"_ref3\",\"mode\",\"environment\",\"stateColor\",\"version\",\"license\",\"readyPodCount\",\"podCount\",\"readyVolumeCount\",\"volumeCount\",\"storageClasses\",\"databaseURL\",\"deleteCommand\",\"describeCommand\",\"Cell\",\"getStateColorDescription\",\"Link\",\"to\",\"float\",\"DeploymentList_DatabaseLinkView\",\"DeploymentList_NoDatabaseLinkView\",\"ListView\",\"_ref4\",\"items\",\"striped\",\"celled\",\"DeploymentList_HeaderView\",\"Body\",\"DeploymentList_RowView\",\"state_color\",\"database_version\",\"database_license\",\"ready_pod_count\",\"pod_count\",\"ready_volume_count\",\"volume_count\",\"storage_classes\",\"database_url\",\"createDescribeCommand\",\"EmptyView\",\"DeploymentList\",\"reloadDeployments\",\"deployments\",\"length\",\"DeploymentList_EmptyView\",\"DeploymentList_ListView\",\"dividing\",\"deployment_DeploymentList\",\"DetailView\",\"match\",\"params\",\"deployment_DeploymentDetails\",\"DeploymentOperator\",\"BrowserRouter\",\"fixed\",\"vertical\",\"position\",\"commonMenuItems\",\"basic\",\"Route\",\"exact\",\"path\",\"component\",\"podInfoView\",\"EndpointView\",\"deploymentName\",\"masterEndpoint\",\"authKeyfileSecretName\",\"authUserSecretName\",\"tlsCACert\",\"tlsCACertSecretName\",\"sub\",\"DetailsView\",\"replication\",\"DeploymentReplicationDetails_EndpointView\",\"source\",\"deployment_name\",\"master_endpoint\",\"auth_keyfile_secret_name\",\"auth_user_secret_name\",\"tls_ca_cert\",\"tls_ca_cert_secret_name\",\"destination\",\"DeploymentReplicationDetails\",\"reloadDeploymentReplications\",\"dr\",\"DeploymentReplicationDetails_DetailsView\",\"DeploymentReplicationList_HeaderView\",\"DeploymentReplicationList_RowView\",\"DeploymentReplicationList\",\"replications\",\"DeploymentReplicationList_EmptyView\",\"DeploymentReplicationList_ListView\",\"replication_DeploymentReplicationList\",\"replication_DeploymentReplicationDetails\",\"DeploymentReplicationOperator\",\"NoOperator\",\"nodeName\",\"capacity\",\"VolumeList_HeaderView\",\"VolumeList_RowView\",\"node_name\",\"VolumeList\",\"reloadVolumes\",\"storageName\",\"volumes\",\"VolumeList_EmptyView\",\"VolumeList_ListView\",\"localPaths\",\"storageClass\",\"storageClassIsDefault\",\"expanded\",\"toggleExpand\",\"index\",\"VolumesRowView\",\"colSpan\",\"storage_VolumeList\",\"StorageRow\",\"onToggleExpand\",\"StorageRow_RowView\",\"StorageRow_VolumesRowView\",\"StorageList_HeaderView\",\"storage_StorageRow\",\"local_paths\",\"storage_class\",\"storage_class_is_default\",\"StorageList\",\"reloadStorages\",\"storages\",\"StorageList_EmptyView\",\"StorageList_ListView\",\"storage_StorageList\",\"StorageOperator\",\"StorageOperator_ListView\",\"PodInfoView\",\"pod\",\"OperatorsView\",\"deploymentReplication\",\"storage\",\"otherOperators\",\"operatorType2Name\",\"Operator\",\"App_PodInfoView\",\"oType\",\"LoadingView\",\"App\",\"operators\",\"reloadOperators\",\"App_OperatorsView\",\"deployment_replication\",\"other\",\"App_LoadingView\",\"isLocalhost\",\"Boolean\",\"window\",\"location\",\"hostname\",\"registerValidSW\",\"swUrl\",\"navigator\",\"serviceWorker\",\"register\",\"then\",\"registration\",\"onupdatefound\",\"installingWorker\",\"installing\",\"onstatechange\",\"controller\",\"console\",\"log\",\"ReactDOM\",\"render\",\"auth_Auth\",\"src_App\",\"document\",\"getElementById\",\"URL\",\"process\",\"origin\",\"addEventListener\",\"response\",\"indexOf\",\"unregister\",\"reload\",\"checkValidServiceWorker\",\"registerServiceWorker\"],\"mappings\":\"iUAAMA,EAAS,oBAER,SAASC,EAAeC,GAC7B,IAAMC,EAAOC,eAAeC,QAAf,GAAAC,OAA0BN,GAA1BM,OAAmCJ,IAChD,GAAIC,EACF,IACE,OAAOI,KAAKC,MAAML,GAClB,MAAOM,KAKN,SAASC,EAAeR,EAAKS,GAElC,OADAP,eAAeQ,QAAf,GAAAN,OAA0BN,GAA1BM,OAAmCJ,GAAOK,KAAKM,UAAUF,IAClDA,ECdF,SAASG,EAAeL,GAC3B,OAAqB,MAAbA,EAAEM,OAGC,IAAAC,EAAA,CACXC,MAAO,GAEDC,cAHK,eAAAC,EAAAC,OAAAC,EAAA,EAAAD,CAAAE,EAAAC,EAAAC,KAAA,SAAAC,EAGSC,GAHT,IAAAC,EAAAC,EAAA,OAAAN,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAE,KAAA,EAIeN,EAAOO,OAJtB,UAIDN,EAJCG,EAAAI,KAKe,MAAlBR,EAAOX,OALJ,CAAAe,EAAAE,KAAA,cAMCJ,EAAUD,EAAQQ,SAGdP,EADkB,MAAlBF,EAAOX,OACG,eAEH,qBAAAT,OAAwBoB,EAAOX,SAGxCK,OAAOgB,OAAO,IAAIC,MAAMT,GAAU,CAAEb,OAAQW,EAAOX,SAdtD,cAAAe,EAAAQ,OAAA,SAgBAX,GAhBA,wBAAAG,EAAAS,SAAAd,MAAA,gBAAAe,GAAA,OAAArB,EAAAsB,MAAAC,KAAAC,YAAA,GAqBLC,IArBK,eAAAC,EAAAzB,OAAAC,EAAA,EAAAD,CAAAE,EAAAC,EAAAC,KAAA,SAAAsB,EAqBDC,GArBC,IAAAC,EAAAtB,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAoB,GAAA,cAAAA,EAAAlB,KAAAkB,EAAAjB,MAAA,cAsBHgB,EAAU,CACVE,OAAU,oBAEVR,KAAKzB,QACL+B,EAAO,cAAP,UAAA1C,OAAqCoC,KAAKzB,QA1BvCgC,EAAAjB,KAAA,EA4BcmB,MAAMJ,EAAU,CAACC,YA5B/B,cA4BDtB,EA5BCuB,EAAAf,KAAAe,EAAAX,OAAA,SA6BAI,KAAKxB,cAAcQ,IA7BnB,wBAAAuB,EAAAV,SAAAO,EAAAJ,SAAA,gBAAAU,GAAA,OAAAP,EAAAJ,MAAAC,KAAAC,YAAA,GAkCLU,KAlCK,eAAAC,EAAAlC,OAAAC,EAAA,EAAAD,CAAAE,EAAAC,EAAAC,KAAA,SAAA+B,EAkCAR,EAAUS,GAlCV,IAAAR,EAAAtB,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAA4B,GAAA,cAAAA,EAAA1B,KAAA0B,EAAAzB,MAAA,cAmCHgB,EAAU,CACVE,OAAU,mBACVQ,eAAgB,oBAEhBhB,KAAKzB,QACL+B,EAAO,cAAP,UAAA1C,OAAqCoC,KAAKzB,QAxCvCwC,EAAAzB,KAAA,EA0CcmB,MAAMJ,EAAU,CACjCY,OAAQ,OACRX,UACAQ,KAAMjD,KAAKM,UAAU2C,KA7ClB,cA0CD9B,EA1CC+B,EAAAvB,KAAAuB,EAAAnB,OAAA,SA+CAI,KAAKxB,cAAcQ,IA/CnB,wBAAA+B,EAAAlB,SAAAgB,EAAAb,SAAA,gBAAAkB,EAAAC,GAAA,OAAAP,EAAAb,MAAAC,KAAAC,YAAA,sBCQAmB,EATC,SAAAC,GAAA,IAAEnC,EAAFmC,EAAEnC,QAAF,OACRoC,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,KACAF,EAAAzC,EAAA0C,cAACE,EAAA,EAAD,CAAQC,UAAQ,EAACC,QAAM,GACrBL,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQF,UAAQ,GAAExC,GAAW,eAE/BoC,EAAAzC,EAAA0C,cAAA,OAAKM,MAAO,CAACC,UAAU,+ICJ/B,IAAMC,EAAY,SAAAV,GAAA,IAAEW,EAAFX,EAAEW,SAAUC,EAAZZ,EAAYY,SAAUC,EAAtBb,EAAsBa,kBAAmBC,EAAzCd,EAAyCc,kBAAmBC,EAA5Df,EAA4De,QAAS3C,EAArE4B,EAAqE5B,MAArE,OAChB6B,EAAAzC,EAAA0C,cAACc,EAAA,EAAD,KACEf,EAAAzC,EAAA0C,cAACe,EAAA,EAAD,CAAMC,SAAUH,GACdd,EAAAzC,EAAA0C,cAACe,EAAA,EAAKE,MAAN,KACElB,EAAAzC,EAAA0C,cAAA,qBACAD,EAAAzC,EAAA0C,cAAA,SAAOkB,MAAM,OAAOxE,MAAO+D,EAAUU,SAAU,SAAC3E,GAAD,OAAOmE,EAAkBnE,EAAE4E,OAAO1E,WAEnFqD,EAAAzC,EAAA0C,cAACe,EAAA,EAAKE,MAAN,KACElB,EAAAzC,EAAA0C,cAAA,yBACAD,EAAAzC,EAAA0C,cAAA,SAAOqB,KAAK,WAAW3E,MAAOgE,EAAUS,SAAU,SAAC3E,GAAD,OAAOoE,EAAkBpE,EAAE4E,OAAO1E,WAEtFqD,EAAAzC,EAAA0C,cAACe,EAAA,EAAKO,OAAN,CAAaC,UAAWC,YAAFC,KAAqBJ,KAAK,YAEhDnD,EAAS6B,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASxD,OAAK,EAACyD,QAASzD,IAAW,OAsCnC0D,6MAjCbC,MAAQ,CACNpB,SAAU,GACVC,SAAU,MAGZoB,YAAc,WACZC,EAAKC,MAAMnB,QAAQkB,EAAKF,MAAMpB,SAAUsB,EAAKF,MAAMnB,mFAG5C,IAAAuB,EAAAxD,KACP,OACEsB,EAAAzC,EAAA0C,cAACkC,EAAA,EAAD,CAAOC,MAAI,GACTpC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAME,OAAP,cACArC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMG,QAAP,KACEtC,EAAAzC,EAAA0C,cAACsC,EAAD,CACEpE,MAAOO,KAAKuD,MAAM9D,MAClBuC,SAAUhC,KAAKoD,MAAMpB,SACrBC,SAAUjC,KAAKoD,MAAMnB,SACrBC,kBAAmB,SAAC4B,GAAD,OAAON,EAAKO,SAAS,CAAC/B,SAAS8B,KAClD3B,kBAAmB,SAAC2B,GAAD,OAAON,EAAKO,SAAS,CAAC9B,SAAS6B,KAClD1B,QAASpC,KAAKqD,eAGlB/B,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMO,QAAP,KACE1C,EAAAzC,EAAA0C,cAACsB,EAAA,EAAD,CAAQoB,MAAM,QAAQC,UAAalE,KAAKoD,MAAMpB,WAAehC,KAAKoD,MAAMnB,SAAYkC,QAASnE,KAAKqD,aAChG/B,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,cADb,mBAzBUC,aCjBLC,EAFOC,IAAMC,mBAAcC,GCMpCC,EAAkB,aAGjB,SAASC,EAASC,GACvB,OAAO,SAA4BtB,GAC/B,OACEjC,EAAAzC,EAAA0C,cAACuD,EAAcC,SAAf,KACG,SAAAC,GAAQ,OAAI1D,EAAAzC,EAAA0C,cAACsD,EAADnG,OAAAgB,OAAA,GAAsB6D,EAAtB,CAA6ByB,SAAUA,YA8E/CC,6MAvEb7B,MAAQ,CACN8B,eAAe,EACfC,aAAa,EACb5G,MAAOhB,EAAeoH,IAAoB,MAqB5CtB,kDAAc,SAAAtE,EAAOiD,EAAUC,GAAjB,IAAAmD,EAAA,OAAAxG,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAEViE,EAAKS,SAAS,CAACtE,WAAMiF,IAFXtF,EAAAE,KAAA,EAGQhB,EAAIqC,KAAK,SAAU,CAAEqB,WAAUC,aAHvC,cAGJmD,EAHIhG,EAAAI,KAIVlB,EAAIC,MAAQ6G,EAAI7G,MAChBP,EAAe2G,EAAiBS,EAAI7G,OACpC+E,EAAKS,SAAS,CACZmB,eAAe,EACf3G,MAAO6G,EAAI7G,QARHa,EAAAQ,OAAA,UAUH,GAVG,eAAAR,EAAAC,KAAA,GAAAD,EAAAiG,GAAAjG,EAAA,SAYVkE,EAAKS,SAAS,CACZmB,eAAe,EACf3G,MAAO,GACPkB,MAAOL,EAAAiG,GAAEnG,UAfDE,EAAAQ,OAAA,UAiBH,GAjBG,yBAAAR,EAAAS,SAAAd,EAAA,4EAqBduG,aAAe,WACbhH,EAAIC,MAAQ,GACZP,EAAe2G,EAAiB,IAChCrB,EAAKS,SAAS,CACZmB,eAAe,EACf3G,MAAO,GACPkB,WAAOiF,gNA3CPpG,EAAIC,MAAQyB,KAAKoD,MAAM7E,eACjBD,EAAI4B,IAAI,yBACdF,KAAK+D,SAAS,CACZmB,eAAe,EACfC,aAAa,EACb5G,MAAOD,EAAIC,wDAGbyB,KAAK+D,SAAS,CACZmB,eAAe,EACfC,aAAa,EACb5G,MAAO,iMAwCX,OACE+C,EAAAzC,EAAA0C,cAACuD,EAAcS,SAAf,CAAwBtH,MAAO+B,KAAKsF,cAChCtF,KAAKoD,MAAM+B,YAAe7D,EAAAzC,EAAA0C,cAACiE,EAAD,MACvBxF,KAAKoD,MAAM8B,cAEZlF,KAAKuD,MAAMkC,SADXnE,EAAAzC,EAAA0C,cAACmE,EAAD,CAAOtD,QAASpC,KAAKqD,YAAa5D,MAAOO,KAAKoD,MAAM3D,gBAhE7C6E,2qCClBZ,IAAM9B,GAAQmD,YAAO,MAAPA,CAAHC,KAMLC,GAAaF,YAAO,OAAPA,CAAHG,KAMVC,GAAeJ,YAAO,MAAPA,CAAHK,KAIZC,GAAaN,YAAO,MAAPA,CAAHO,KAIVC,GAAYR,YAAO,OAAPA,CAAHS,KAUTC,GAAoBV,YAAO,OAAPA,CAAHW,KAQjBC,GAAaZ,YAAOa,IAAPb,CAAHc,KAOVC,GAAmBf,YAAO,MAAPA,CAAHgB,mCCTdC,8MAlCbxD,MAAQ,CAACM,MAAK,KAEdmD,MAAQ,WAAQvD,EAAKS,SAAS,CAACL,MAAK,OACpCA,KAAO,WAAQJ,EAAKS,SAAS,CAACL,MAAK,6EAGjC,OACEpC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAD,CAAOqD,QAAS9G,KAAKuD,MAAMuD,QAASC,QAAS/G,KAAK6G,MAAOG,OAAQhH,KAAK0D,KAAMA,KAAM1D,KAAKoD,MAAMM,MAC3FpC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAME,OAAP,KAAe3D,KAAKuD,MAAM0D,OAC1B3F,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMG,QAAP,KACEtC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMyD,YAAP,KACE5F,EAAAzC,EAAA0C,cAAA,SACGvB,KAAKuD,MAAM4D,aAEd7F,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAAS4F,UAAQ,GACf9F,EAAAzC,EAAA0C,cAAA,YAAOvB,KAAKuD,MAAM8D,YAIxB/F,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMO,QAAP,KACE1C,EAAAzC,EAAA0C,cAAC+F,GAAA,gBAAD,CAAiBC,KAAMvH,KAAKuD,MAAM8D,QAASG,OAAQxH,KAAK6G,OACtDvF,EAAAzC,EAAA0C,cAACsB,EAAA,EAAD,CACE4E,UAAQ,EACRC,KAAK,OACLC,cAAc,QACdzE,QAAQ,mBA1BWoB,aCE3BsD,GAAiB,SAAAvG,GAAA,IAAEwG,EAAFxG,EAAEwG,MAAOC,EAATzG,EAASyG,eAAgB3D,EAAzB9C,EAAyB8C,QAAS4D,EAAlC1G,EAAkC0G,QAASC,EAA3C3G,EAA2C2G,UAA3C,OACrB1G,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,KACEF,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,KAASkE,GACTvG,EAAAzC,EAAA0C,cAAC0G,GAAA,EAAD,CAAMC,SAAO,GACVH,EAAQI,IAAI,SAAC1K,GAAD,OAAU6D,EAAAzC,EAAA0C,cAAC6G,GAAD,CAAY5K,IAAKC,EAAK4K,GAAIC,WAAY7K,EAAMkE,OAAQlE,EAAK4K,KAAOP,EAAgB3D,QAASA,EAAS6D,UAAWA,SAiBpIO,GAAa,SAAAC,GAAA,IAyEcC,EArFJC,EAYRJ,EAAFE,EAAEF,WAAYN,EAAdQ,EAAcR,UAAWrG,EAAzB6G,EAAyB7G,OAAQwC,EAAjCqE,EAAiCrE,QAAjC,OACjB7C,EAAAzC,EAAA0C,cAAC0G,GAAA,EAAKU,KAAN,KACErH,EAAAzC,EAAA0C,cAACqH,GAAA,EAAD,KACEtH,EAAAzC,EAAA0C,cAACqH,GAAA,EAAUC,MAAX,CAAiBlH,OAAQA,EAAQwC,QAAS,kBAAMA,EAAQmE,EAAWD,MACjE/G,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,aADb,IAC2B/C,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAMiE,EAAWQ,MAAQ,QAAU,SADpE,IACgFR,EAAWD,IAE3F/G,EAAAzC,EAAA0C,cAACqH,GAAA,EAAUhF,QAAX,CAAmBjC,OAAQA,GAjBP,WADG+G,EAmBAJ,EAAWS,mBAjB7B,KAGPzH,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,wBACqB,SAAnB6C,EAA6B,MAAQ,MAarCpH,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,aACCyC,EAAWU,UAAY,IACtBV,EAAWU,SACX1H,EAAAzC,EAAA0C,cAAC0E,GAAD,KACE3E,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,2BACzBgD,QAAS8B,GAAoBb,EAAWU,SAAUhB,GAClDf,MAAM,eACNE,YAAY,4CAEd7F,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAAS+B,GAAyBd,EAAWU,SAAUhB,GACvDf,MAAM,2BACNE,YAAY,4DAGhB,MAEJ7F,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,aACCyC,EAAWe,UAAY,IACxB/H,EAAAzC,EAAA0C,cAAC0E,GAAD,KACIqC,EAAWe,SACX/H,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAASiC,GAAyBhB,EAAWe,SAAUrB,GACvDf,MAAM,6CACNE,YAAY,2DAEd,OAGN7F,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,YACCyC,EAAWiB,SAAW,IACvBjI,EAAAzC,EAAA0C,cAAC0E,GAAD,KACIqC,EAAWiB,QACXjI,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,SAwBiBoB,EAxBgBH,EAAWiB,QAyB1D,uBAAA3L,OAA8B6K,IAxBhBxB,MAAM,wCACNE,YAAY,0DAEd,WAQd,SAASiC,GAAyBI,EAASxB,GACzC,iCAAApK,OAAkCoK,EAAlC,KAAApK,OAA+C4L,GAGjD,SAASL,GAAoBK,EAASxB,GACpC,yBAAApK,OAA0BoK,EAA1B,KAAApK,OAAuC4L,GAGzC,SAASF,GAAyBG,EAASzB,GACzC,iCAAApK,OAAkCoK,EAAlC,KAAApK,OAA+C6L,OAyBlCC,8MAjBbtG,MAAQ,KAERe,QAAU,SAACkE,GACT/E,EAAKS,SAAS,CAAC+D,eAAgBxE,EAAKF,MAAM0E,iBAAmBO,EAAM,KAAOA,6EAI1E,OAAQ/G,EAAAzC,EAAA0C,cAACoI,GAAD,CACN9B,MAAO7H,KAAKuD,MAAMsE,MAClBE,QAAS/H,KAAKuD,MAAMwE,QACpBD,eAAgB9H,KAAKoD,MAAM0E,eAC3B3D,QAASnE,KAAKmE,QACd6D,UAAWhI,KAAKuD,MAAMyE,mBAbH1D,aC9FnBsF,GAAmB,SAAAvI,GAAA,IAAEwI,EAAFxI,EAAEwI,aAAc7B,EAAhB3G,EAAgB2G,UAAhB,OACvB1G,EAAAzC,EAAA0C,cAAA,WACGsI,EAAa1B,IAAI,SAAC1K,GAAD,OAAU6D,EAAAzC,EAAA0C,cAACuI,GAAD,CAC1BtM,IAAKC,EAAKoK,MACVA,MAAOpK,EAAKoK,MACZE,QAAStK,EAAKsK,QACdC,UAAWA,QAKX+B,8MACJ3G,MAAQ,CACN4G,SAAS,EACTvK,WAAOiF,KAOTuF,sCAAmB,SAAAlL,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAEfiE,EAAKS,SAAS,CACZiG,SAAS,IAHI5K,EAAAE,KAAA,EAKMhB,EAAI4B,IAAJ,mBAAAtC,OAA2B0F,EAAKC,MAAMc,OAL5C,OAKTrF,EALSI,EAAAI,KAMf8D,EAAKS,SAAS,CACZmG,WAAYlL,EACZgL,SAAS,EACTvK,WAAOiF,IATMtF,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAYfkE,EAAKS,SAAS,CACZiG,SAAS,EACTvK,MAAOL,EAAAiG,GAAEnG,WAEPd,EAAcgB,EAAAiG,IAhBH,CAAAjG,EAAAE,KAAA,gBAiBbgE,EAAKC,MAAMyB,WAjBE5F,EAAAQ,OAAA,kBAqBjB0D,EAAKC,MAAM4G,WAAW7G,EAAK2G,iBAAkB,KArB5B,yBAAA7K,EAAAS,SAAAd,EAAA,oGAHjBiB,KAAKiK,oDA4BL,IAAMG,EAAIpK,KAAKoD,MAAM8G,WACrB,OAAKE,EAIH9I,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAAC4E,GAAD,KAAW7E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQ3B,KAAKoD,MAAM4G,QAASM,QAAM,KACjEhJ,EAAAzC,EAAA0C,cAACgJ,GAAD,CAAkBV,aAAcO,EAAEI,cAAexC,UAAWoC,EAAEpC,aALxD1G,EAAAzC,EAAA0C,cAACiE,EAAD,aArCkBlB,aAgDjBmG,OAAa7F,EAASmF,yBC1D/BW,GAAa,SAAArJ,GAAA,IAAE2I,EAAF3I,EAAE2I,QAAF,OACjB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMhH,OAAP,KACErC,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,cACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,gBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,KAAkBvJ,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAA,qBAAhB,kBAClBD,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,KAAkBvJ,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAA,wBAAhB,kBAClBD,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,qBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,eAEEvJ,EAAAzC,EAAA0C,cAAC8E,GAAD,KAAW/E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQqI,EAASM,QAAM,SAMxDS,GAAmB,SAAAvC,GAAA,IAAEnE,EAAFmE,EAAEnE,KAAM2G,EAARxC,EAAQwC,IAAR,OACvB1J,EAAAzC,EAAA0C,cAAA,KAAG0J,KAAMD,EAAKrI,OAAQ0B,GACpB/C,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,cAAhC,wCAME6G,GAAqB,kBACzB5J,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,cAAhC,qEAKI8G,GAAU,SAAAC,GAAA,IAAE/G,EAAF+G,EAAE/G,KAAMgH,EAARD,EAAQC,KAAMC,EAAdF,EAAcE,YAAaC,EAA3BH,EAA2BG,WAAYC,EAAvCJ,EAAuCI,QAASC,EAAhDL,EAAgDK,QAASC,EAAzDN,EAAyDM,cAAeC,EAAxEP,EAAwEO,SAAUC,EAAlFR,EAAkFQ,iBAAkBC,EAApGT,EAAoGS,YAAaC,EAAjHV,EAAiHU,eAAgBC,EAAjIX,EAAiIW,YAAaC,EAA9IZ,EAA8IY,cAAeC,EAA7Jb,EAA6Ja,gBAA7J,OACd3K,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAoB,UAAbkH,EAAwB,QAAU,OAAQtH,MAAOsH,KAqFpF,SAAkCA,GAChC,OAAQA,GACN,IAAK,QACH,MAAO,gCACT,IAAK,SACH,MAAO,gEACT,IAAK,SACH,MAAO,uGACT,IAAK,MACH,MAAO,6EACT,QACE,MAAO,uBA/FJY,CAAyBZ,KAG9BjK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAAC6K,EAAA,EAAD,CAAMC,GAAE,eAAAzO,OAAiByG,IACtBA,IAGL/C,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACGb,EACD/J,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACJ,gBAAdhB,EAA+BhK,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,WAAYnB,QAAQ,4BAA6B,KAC7F,eAAdoI,EAA8BhK,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,cAAenB,QAAQ,2BAA4B,OAGlH5B,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACGV,EACDlK,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACR,cAAVb,EAAyBnK,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,UAAWnB,QAAQ,sBAAuB,KACpF,eAAVuI,EAA0BnK,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,WAAYnB,QAAQ,uBAAwB,OAGvG5B,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KAAaR,EAAb,MAA+BC,GAC/BrK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KAAaN,EAAb,MAAkCC,GAClCvK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KAAaJ,EAAe3D,IAAI,SAAC1K,GAAD,MAAoB,KAATA,EAAe,YAAcA,KACxE6D,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACIH,EAAczK,EAAAzC,EAAA0C,cAACgL,GAAD,CAAkBlI,KAAMA,EAAM2G,IAAKe,IAAiBzK,EAAAzC,EAAA0C,cAACiL,GAAD,MACpElL,EAAAzC,EAAA0C,cAAC0H,GAAD,CACInC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAAS4E,EACThF,MAAM,sBACNE,YAAY,kEAEhB7F,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACpBhL,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,UACzBgD,QAAS2E,EACT/E,MAAM,oBACNE,YAAY,wCAOhBsF,GAAW,SAAAC,GAAA,IAAEC,EAAFD,EAAEC,MAAO3C,EAAT0C,EAAS1C,QAAT,OACf1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAD,CAAOiC,SAAO,EAACC,QAAM,GACnBvL,EAAAzC,EAAA0C,cAACuL,GAAD,CAAY9C,QAASA,IACrB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMoC,KAAP,KAEKJ,EAASA,EAAMxE,IAAI,SAAC1K,GAAD,OAClB6D,EAAAzC,EAAA0C,cAACyL,GAAD,CACExP,IAAKC,EAAK4G,KACVA,KAAM5G,EAAK4G,KACX2D,UAAWvK,EAAKuK,UAChBqD,KAAM5N,EAAK4N,KACXC,YAAa7N,EAAK6N,YAClBC,WAAY9N,EAAKwP,YACjBzB,QAAS/N,EAAKyP,iBACdzB,QAAShO,EAAK0P,iBACdzB,cAAejO,EAAK2P,gBACpBzB,SAAUlO,EAAK4P,UACfzB,iBAAkBnO,EAAK6P,mBACvBzB,YAAapO,EAAK8P,aAClBzB,eAAgBrO,EAAK+P,gBACrBzB,YAAatO,EAAKgQ,aAClBzB,eAUiB3H,EAVkB5G,EAAK4G,KAUjB2D,EAVuBvK,EAAKuK,UAW7D,sCAAApK,OAA6CoK,EAA7C,KAAApK,OAA0DyG,IAVhD4H,gBAAiByB,GAAsBjQ,EAAK4G,KAAM5G,EAAKuK,aASnE,IAA6B3D,EAAM2D,IARnB1G,EAAAzC,EAAA0C,cAAA,wBAMVoM,GAAY,kBAAOrM,EAAAzC,EAAA0C,cAAA,8BAMzB,SAASmM,GAAsBrJ,EAAM2D,GACnC,8CAAApK,OAA+CoK,EAA/C,KAAApK,OAA4DyG,OAkBxDuJ,8MACJxK,MAAQ,CACNuJ,MAAO,KACPlN,MAAO,KACPuK,SAAS,KAOX6D,uCAAoB,SAAA9O,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAEhBiE,EAAKS,SAAS,CAACiG,SAAS,IAFR5K,EAAAE,KAAA,EAGKhB,EAAI4B,IAAI,mBAHb,OAGVlB,EAHUI,EAAAI,KAIhB8D,EAAKS,SAAS,CACZ4I,MAAO3N,EAAO8O,YACd9D,SAAS,EACTvK,MAAO,OAPOL,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAUhBkE,EAAKS,SAAS,CAACtE,MAAOL,EAAAiG,GAAEnG,QAAS8K,SAAS,KACtC5L,EAAcgB,EAAAiG,IAXF,CAAAjG,EAAAE,KAAA,gBAYdgE,EAAKC,MAAMyB,WAZG5F,EAAAQ,OAAA,kBAgBlB0D,EAAKC,MAAM4G,WAAW7G,EAAKuK,kBAAmB,KAhB5B,yBAAAzO,EAAAS,SAAAd,EAAA,oGAHlBiB,KAAK6N,qDAuBL,IAAMlB,EAAQ3M,KAAKoD,MAAMuJ,MACzB,OAAKA,EAGgB,IAAjBA,EAAMoB,OACAzM,EAAAzC,EAAA0C,cAACyM,GAAD,MAEF1M,EAAAzC,EAAA0C,cAAC0M,GAAD,CAAUtB,MAAOA,EAAO3C,QAAShK,KAAKoD,MAAM4G,UAL1C1I,EAAAzC,EAAA0C,cAACiE,EAAD,aAjCelB,aA0CdmG,OAAa7F,EAASgJ,KCnL/BnB,GAAW,kBACfnL,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQuK,UAAQ,GAAhB,8BAGA5M,EAAAzC,EAAA0C,cAAC4M,GAAD,QAIEC,GAAa,SAAA/M,GAAA,IAAEgN,EAAFhN,EAAEgN,MAAF,OACjB/M,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQuK,UAAQ,GAAhB,oBACoBG,EAAMC,OAAOjK,MAEjC/C,EAAAzC,EAAA0C,cAACgN,GAAD,CAAmBlK,KAAMgK,EAAMC,OAAOjK,SA2C3BmK,oLAtCJ,IAAAlL,EAAAtD,KACP,OACEsB,EAAAzC,EAAA0C,cAACkN,EAAA,EAAD,KACEnN,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACuD,EAAcC,SAAf,KACG,SAAAC,GAAQ,OACP1D,EAAAzC,EAAA0C,cAACgF,GAAD,CAAYmI,MAAM,OAAOC,UAAQ,GAC/BrN,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACErH,EAAAzC,EAAA0C,cAACiF,EAAA,EAAK7C,OAAN,4BACErC,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKA,KAAN,KACElF,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACErH,EAAAzC,EAAA0C,cAAC6K,EAAA,EAAD,CAAMC,GAAG,KAAT,gBAEF/K,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,CAAWiG,SAAS,QAAQzK,QAAS,kBAAMa,MAA3C,WAIH1B,EAAKC,MAAMsL,oBAKpBvN,EAAAzC,EAAA0C,cAACmF,GAAD,KACEpF,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,EAAC1H,UAAQ,GACnB9F,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACwN,EAAA,EAAD,CAAOC,OAAK,EAACC,KAAK,IAAIC,UAAWzC,KACjCnL,EAAAzC,EAAA0C,cAACwN,EAAA,EAAD,CAAOE,KAAK,oBAAoBC,UAAWd,OAGhDpO,KAAKuD,MAAM4L,YACVnP,KAAKuD,MAAM9D,MAAS6B,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,GAACxN,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASxD,OAAK,EAACyD,QAASlD,KAAKuD,MAAM9D,SAAqB,eA/BxE6E,aCjB3B8K,GAAe,SAAA/N,GAAA,IAAE4F,EAAF5F,EAAE4F,MAAOoI,EAAThO,EAASgO,eAAgBC,EAAzBjO,EAAyBiO,eAAgBC,EAAzClO,EAAyCkO,sBAAuBC,EAAhEnO,EAAgEmO,mBAAoBC,EAApFpO,EAAoFoO,UAAWC,EAA/FrO,EAA+FqO,oBAA/F,OACnBpO,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,KACEF,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,KAASsD,GACT3F,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,mBACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAKsJ,GAAkB,MAEzB/N,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,wBACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAKuJ,GAAkB,MAEzBhO,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,2BACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAIzE,EAAAzC,EAAA0C,cAAA,YAAOkO,KAEbnO,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQgM,KAAG,GAAX,gBACArO,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,+BACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAIzE,EAAAzC,EAAA0C,cAAA,YAAOgO,GAAyB,OAEtCjO,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,4BACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAIzE,EAAAzC,EAAA0C,cAAA,YAAOiO,GAAsB,OAEnClO,EAAAzC,EAAA0C,cAACiB,GAAD,KACElB,EAAAzC,EAAA0C,cAACsE,GAAD,2BACAvE,EAAAzC,EAAA0C,cAACwE,GAAD,KAAIzE,EAAAzC,EAAA0C,cAAA,YAAOmO,GAAuB,SAKlCE,GAAc,SAAApH,GAAA,IAAEqH,EAAFrH,EAAEqH,YAAa7F,EAAfxB,EAAewB,QAAf,OAClB1I,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAAC4E,GAAD,KAAW7E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQqI,EAASM,QAAM,KACtDhJ,EAAAzC,EAAA0C,cAACuO,GAAD,CACE7I,MAAM,SACNoI,eAAgBQ,EAAYE,OAAOC,gBACnCV,eAAgBO,EAAYE,OAAOE,gBACnCV,sBAAuBM,EAAYE,OAAOG,yBAC1CV,mBAAoBK,EAAYE,OAAOI,sBACvCV,UAAWI,EAAYE,OAAOK,YAC9BV,oBAAqBG,EAAYE,OAAOM,0BAE1C/O,EAAAzC,EAAA0C,cAACuO,GAAD,CACE7I,MAAM,cACNoI,eAAgBQ,EAAYS,YAAYN,gBACxCV,eAAgBO,EAAYS,YAAYL,gBACxCV,sBAAuBM,EAAYS,YAAYJ,yBAC/CV,mBAAoBK,EAAYS,YAAYH,sBAC5CV,UAAWI,EAAYS,YAAYF,YACnCV,oBAAqBG,EAAYS,YAAYD,4BAK7CE,8MACJnN,MAAQ,CACN4G,SAAS,EACTvK,WAAOiF,KAOT8L,kDAA+B,SAAAzR,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAE3BiE,EAAKS,SAAS,CACZiG,SAAS,IAHgB5K,EAAAE,KAAA,EAKNhB,EAAI4B,IAAJ,+BAAAtC,OAAuC0F,EAAKC,MAAMc,OAL5C,OAKrBrF,EALqBI,EAAAI,KAM3B8D,EAAKS,SAAS,CACZ8L,YAAa7Q,EACbgL,SAAS,EACTvK,WAAOiF,IATkBtF,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAY3BkE,EAAKS,SAAS,CACZiG,SAAS,EACTvK,MAAOL,EAAAiG,GAAEnG,WAEPd,EAAcgB,EAAAiG,IAhBS,CAAAjG,EAAAE,KAAA,gBAiBzBgE,EAAKC,MAAMyB,WAjBc5F,EAAAQ,OAAA,kBAqB7B0D,EAAKC,MAAM4G,WAAW7G,EAAKkN,6BAA8B,KArB5B,yBAAApR,EAAAS,SAAAd,EAAA,oGAH7BiB,KAAKwQ,gEA4BL,IAAMC,EAAKzQ,KAAKoD,MAAMyM,YACtB,OAAKY,EAGGnP,EAAAzC,EAAA0C,cAACmP,GAAD,CAAab,YAAaY,EAAIzG,QAAShK,KAAKoD,MAAM4G,UAFhD1I,EAAAzC,EAAA0C,cAACiE,EAAD,aArC6BlB,aA2C5BmG,OAAa7F,EAAS2L,KCjG/B7F,GAAa,SAAArJ,GAAA,IAAE2I,EAAF3I,EAAE2I,QAAF,OACjB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMhH,OAAP,KACErC,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,cACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,eACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,oBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,eAEEvJ,EAAAzC,EAAA0C,cAAC8E,GAAD,KAAW/E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQqI,EAASM,QAAM,SAMxDa,GAAU,SAAA3C,GAAA,IAAEnE,EAAFmE,EAAEnE,KAAYkH,GAAd/C,EAAQ6C,KAAR7C,EAAc+C,YAAYwE,EAA1BvH,EAA0BuH,OAAQO,EAAlC9H,EAAkC8H,YAAatE,EAA/CxD,EAA+CwD,cAAeC,EAA9DzD,EAA8DyD,gBAA9D,OACd3K,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAoB,UAAbkH,EAAwB,QAAU,OAAQtH,MAAOsH,KAiEpF,SAAkCA,GAChC,OAAQA,GACN,IAAK,QACH,MAAO,mCACT,IAAK,SACH,MAAO,mCACT,IAAK,MACH,MAAO,8EACT,QACE,MAAO,uBAzEJY,CAAyBZ,KAG9BjK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAAC6K,EAAA,EAAD,CAAMC,GAAE,2BAAAzO,OAA6ByG,IAClCA,IAGL/C,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACG6D,GAEHzO,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACGoE,GAEHhP,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAAC0H,GAAD,CACInC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAAS4E,EACThF,MAAM,kCACNE,YAAY,8EAEhB7F,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACpBhL,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,UACzBgD,QAAS2E,EACT/E,MAAM,gCACNE,YAAY,oDAOhBsF,GAAW,SAAArB,GAAA,IAAEuB,EAAFvB,EAAEuB,MAAO3C,EAAToB,EAASpB,QAAT,OACf1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAD,CAAOiC,SAAO,EAACC,QAAM,GACnBvL,EAAAzC,EAAA0C,cAACoP,GAAD,CAAY3G,QAASA,IACrB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMoC,KAAP,KAEKJ,EAASA,EAAMxE,IAAI,SAAC1K,GAAD,OAClB6D,EAAAzC,EAAA0C,cAACqP,GAAD,CACEpT,IAAKC,EAAK4G,KACVA,KAAM5G,EAAK4G,KACX2D,UAAWvK,EAAKuK,UAChBuD,WAAY9N,EAAKwP,YACjB8C,OAAQtS,EAAKsS,OAAOC,iBAAmBvS,EAAKsS,OAAOE,gBACnDK,YAAa7S,EAAK6S,YAAYN,iBAAmBvS,EAAK6S,YAAYL,gBAClEjE,eAUiB3H,EAVkB5G,EAAK4G,KAUjB2D,EAVuBvK,EAAKuK,UAW7D,iDAAApK,OAAwDoK,EAAxD,KAAApK,OAAqEyG,IAV3D4H,gBAAiByB,GAAsBjQ,EAAK4G,KAAM5G,EAAKuK,aASnE,IAA6B3D,EAAM2D,IARnB1G,EAAAzC,EAAA0C,cAAA,wBAMVoM,GAAY,kBAAOrM,EAAAzC,EAAA0C,cAAA,0CAMzB,SAASmM,GAAsBrJ,EAAM2D,GACnC,yDAAApK,OAA0DoK,EAA1D,KAAApK,OAAuEyG,OAgBnEwM,8MACJzN,MAAQ,CACNuJ,MAAO,KACPlN,MAAO,KACPuK,SAAS,KAOXwG,kDAA+B,SAAAzR,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAE3BiE,EAAKS,SAAS,CAACiG,SAAS,IAFG5K,EAAAE,KAAA,EAGNhB,EAAI4B,IAAI,+BAHF,OAGrBlB,EAHqBI,EAAAI,KAI3B8D,EAAKS,SAAS,CACZ4I,MAAO3N,EAAO8R,aACd9G,SAAS,EACTvK,MAAO,OAPkBL,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAU3BkE,EAAKS,SAAS,CAACtE,MAAOL,EAAAiG,GAAEnG,QAAS8K,SAAS,KACtC5L,EAAcgB,EAAAiG,IAXS,CAAAjG,EAAAE,KAAA,gBAYzBgE,EAAKC,MAAMyB,WAZc5F,EAAAQ,OAAA,kBAgB7B0D,EAAKC,MAAM4G,WAAW7G,EAAKkN,6BAA8B,KAhB5B,yBAAApR,EAAAS,SAAAd,EAAA,oGAH7BiB,KAAKwQ,gEAuBL,IAAM7D,EAAQ3M,KAAKoD,MAAMuJ,MACzB,OAAKA,EAGgB,IAAjBA,EAAMoB,OACAzM,EAAAzC,EAAA0C,cAACwP,GAAD,MAEFzP,EAAAzC,EAAA0C,cAACyP,GAAD,CAAUrE,MAAOA,EAAO3C,QAAShK,KAAKoD,MAAM4G,UAL1C1I,EAAAzC,EAAA0C,cAACiE,EAAD,aAjC0BlB,aA0CzBmG,OAAa7F,EAASiM,KC5I/BpE,GAAW,kBACfnL,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQuK,UAAQ,GAAhB,yCAGA5M,EAAAzC,EAAA0C,cAAC0P,GAAD,QAIE7C,GAAa,SAAA/M,GAAA,IAAEgN,EAAFhN,EAAEgN,MAAF,OACjB/M,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQuK,UAAQ,GAAhB,+BAC+BG,EAAMC,OAAOjK,MAE5C/C,EAAAzC,EAAA0C,cAAC2P,GAAD,CAA8B7M,KAAMgK,EAAMC,OAAOjK,SA2CtC8M,oLAtCJ,IAAA7N,EAAAtD,KACP,OACEsB,EAAAzC,EAAA0C,cAACkN,EAAA,EAAD,KACEnN,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACuD,EAAcC,SAAf,KACG,SAAAC,GAAQ,OACP1D,EAAAzC,EAAA0C,cAACgF,GAAD,CAAYmI,MAAM,OAAOC,UAAQ,GAC/BrN,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACErH,EAAAzC,EAAA0C,cAACiF,EAAA,EAAK7C,OAAN,wCACArC,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKA,KAAN,KACElF,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACErH,EAAAzC,EAAA0C,cAAC6K,EAAA,EAAD,CAAMC,GAAG,KAAT,4BAEF/K,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,CAAWiG,SAAS,QAAQzK,QAAS,kBAAMa,MAA3C,YAKH1B,EAAKC,MAAMsL,mBAIlBvN,EAAAzC,EAAA0C,cAACmF,GAAD,KACEpF,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,EAAC1H,UAAQ,GACnB9F,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACwN,EAAA,EAAD,CAAOC,OAAK,EAACC,KAAK,IAAIC,UAAWzC,KACjCnL,EAAAzC,EAAA0C,cAACwN,EAAA,EAAD,CAAOE,KAAK,gCAAgCC,UAAWd,OAG5DpO,KAAKuD,MAAM4L,YACVnP,KAAKuD,MAAM9D,MAAS6B,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,GAACxN,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASxD,OAAK,EAACyD,QAASlD,KAAKuD,MAAM9D,SAAqB,eA/B7D6E,aCP7B8M,GAjBI,kBACjB9P,EAAAzC,EAAA0C,cAACc,EAAA,EAAD,KACEf,EAAAzC,EAAA0C,cAACkC,EAAA,EAAD,CAAOC,MAAI,GACTpC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAME,OAAP,iCACArC,EAAAzC,EAAA0C,cAACkC,EAAA,EAAMG,QAAP,KACEtC,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,GACZxN,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASgB,MAAM,UAAf,mDAIIV,MAAM4L,qBACL5L,MAAM9D,MAAS6B,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASxD,OAAK,EAACyD,iBAAcK,MAAM9D,QAAW,SCJtEiL,GAAa,SAAArJ,GAAA,IAAE2I,EAAF3I,EAAE2I,QAAF,OACjB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMhH,OAAP,KACErC,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,cACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,iBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,eAEEvJ,EAAAzC,EAAA0C,cAAC8E,GAAD,KAAW/E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQqI,EAASM,QAAM,SAMxDa,GAAU,SAAA3C,GAAA,IAAEnE,EAAFmE,EAAEnE,KAAMkH,EAAR/C,EAAQ+C,WAAY8F,EAApB7I,EAAoB6I,SAAUC,EAA9B9I,EAA8B8I,SAAUrF,EAAxCzD,EAAwCyD,gBAAiBD,EAAzDxD,EAAyDwD,cAAzD,OACd1K,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAoB,UAAbkH,EAAwB,QAAU,OAAQtH,MAAOsH,KA+DpF,SAAkCA,GAChC,OAAQA,GACN,IAAK,QACH,MAAO,gCACT,IAAK,SACH,MAAO,sEACT,IAAK,SACH,MAAO,6GACT,IAAK,MACH,MAAO,mFACT,QACE,MAAO,uBAzEJY,CAAyBZ,KAG9BjK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACG7H,GAEH/C,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACGoF,GAEHhQ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACGmF,GAEH/P,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAAC0H,GAAD,CACInC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAAS4E,EACThF,MAAM,4BACNE,YAAY,wEAEhB7F,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACpBhL,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,UACzBgD,QAAS2E,EACT/E,MAAM,0BACNE,YAAY,8CAOhBsF,GAAW,SAAArB,GAAA,IAAEuB,EAAFvB,EAAEuB,MAAO3C,EAAToB,EAASpB,QAAT,OACf1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAD,CAAOkC,QAAM,GACXvL,EAAAzC,EAAA0C,cAACgQ,GAAD,CAAYvH,QAASA,IACrB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMoC,KAAP,KAEKJ,EAASA,EAAMxE,IAAI,SAAC1K,GAAD,OAClB6D,EAAAzC,EAAA0C,cAACiQ,GAAD,CACEhU,IAAKC,EAAK4G,KACVA,KAAM5G,EAAK4G,KACXkH,WAAY9N,EAAKwP,YACjBoE,SAAU5T,EAAKgU,UACfH,SAAU7T,EAAK6T,SACftF,eAWiB3H,EAXkB5G,EAAK4G,KAYlD,mCAAAzG,OAA0CyG,IAXhC4H,gBAAiByB,GAAsBjQ,EAAK4G,QAUxD,IAA6BA,IARjB/C,EAAAzC,EAAA0C,cAAA,wBAMNoM,GAAY,kBAAOrM,EAAAzC,EAAA0C,cAAA,oCAMzB,SAASmM,GAAsBrJ,GAC7B,2CAAAzG,OAA4CyG,OAkBxCqN,8MACJtO,MAAQ,CACNuJ,WAAOjI,EACPjF,WAAOiF,EACPsF,SAAS,KAOX2H,mCAAgB,SAAA5S,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAEZiE,EAAKS,SAAS,CACZiG,SAAS,IAHC5K,EAAAE,KAAA,EAKShB,EAAI4B,IAAJ,gBAAAtC,OAAwB0F,EAAKC,MAAMqO,cAL5C,OAKN5S,EALMI,EAAAI,KAMZ8D,EAAKS,SAAS,CACZ4I,MAAO3N,EAAO6S,QACd7H,SAAS,EACTvK,WAAOiF,IATGtF,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAYZkE,EAAKS,SAAS,CACZtE,MAAOL,EAAAiG,GAAEnG,QACT8K,SAAS,KAEP5L,EAAcgB,EAAAiG,IAhBN,CAAAjG,EAAAE,KAAA,gBAiBVgE,EAAKC,MAAMyB,WAjBD5F,EAAAQ,OAAA,kBAqBd0D,EAAKC,MAAM4G,WAAW7G,EAAKqO,cAAe,KArB5B,yBAAAvS,EAAAS,SAAAd,EAAA,oGAHdiB,KAAK2R,iDA4BL,IAAMhF,EAAQ3M,KAAKoD,MAAMuJ,MACzB,OAAKA,EAGgB,IAAjBA,EAAMoB,OACAzM,EAAAzC,EAAA0C,cAACuQ,GAAD,MAEFxQ,EAAAzC,EAAA0C,cAACwQ,GAAD,CAAUpF,MAAOA,EAAO3C,QAAShK,KAAKoD,MAAM4G,UAL1C1I,EAAAzC,EAAA0C,cAACiE,EAAD,aAtCWlB,aA+CVmG,OAAa7F,EAAS8M,KCnJ/BvG,GAAU,SAAA9J,GAAA,IAAEgD,EAAFhD,EAAEgD,KAAMkH,EAARlK,EAAQkK,WAAWyG,EAAnB3Q,EAAmB2Q,WAAYC,EAA/B5Q,EAA+B4Q,aAAcC,EAA7C7Q,EAA6C6Q,sBAAuBlG,EAApE3K,EAAoE2K,cAAeC,EAAnF5K,EAAmF4K,gBAAiBkG,EAApG9Q,EAAoG8Q,SAAUC,EAA9G/Q,EAA8G+Q,aAA9G,OACd9Q,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAoB,UAAbkH,EAAwB,QAAU,OAAQtH,MAAOsH,KA6EpF,SAAkCA,GAChC,OAAQA,GACN,IAAK,QACH,MAAO,gCACT,IAAK,SACH,MAAO,mEACT,IAAK,SACH,MAAO,0GACT,IAAK,MACH,MAAO,gFACT,QACE,MAAO,uBAvFJY,CAAyBZ,KAG9BjK,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,CAAY/H,QAASiO,GACnB9Q,EAAAzC,EAAA0C,cAACqH,GAAA,EAAD,KACEtH,EAAAzC,EAAA0C,cAACqH,GAAA,EAAUC,MAAX,CAAiBlH,OAAQwQ,GACvB7Q,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,aACVA,KAIP/C,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACG8F,EAAW7J,IAAI,SAAC1K,EAAM4U,GAAP,OAAiB/Q,EAAAzC,EAAA0C,cAAA,QAAM/D,IAAK6U,GAAQ5U,MAEtD6D,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACG+F,EACD3Q,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACnB4F,GAAyB5Q,EAAAzC,EAAA0C,cAACuJ,GAAA,EAAD,CAAOhE,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAMC,KAAK,gBAAiBnB,QAAQ,4BAGlF5B,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,KACE5K,EAAAzC,EAAA0C,cAAC0H,GAAD,CACInC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,SACzBgD,QAAS4E,EACThF,MAAM,yBACNE,YAAY,qEAEhB7F,EAAAzC,EAAA0C,cAAA,QAAMM,MAAO,CAACyK,MAAQ,UACpBhL,EAAAzC,EAAA0C,cAAC0H,GAAD,CACEnC,QAASxF,EAAAzC,EAAA0C,cAAC6C,EAAA,EAAD,CAAM8E,MAAI,EAAC7E,KAAK,UACzBgD,QAAS2E,EACT/E,MAAM,uBACNE,YAAY,2CAOhBmL,GAAiB,SAAA9J,GAAA,IAAEnE,EAAFmE,EAAEnE,KAAF,OACrB/C,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMuB,KAAP,CAAYqG,QAAQ,KAClBjR,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQgM,KAAG,GAAX,WACArO,EAAAzC,EAAA0C,cAACiR,GAAD,CAAYZ,YAAavN,OAgDhBoO,kNA1CbrP,MAAQ,CAAC+O,UAAU,KAEnBO,eAAiB,WAAQpP,EAAKS,SAAS,CAACoO,UAAW7O,EAAKF,MAAM+O,oFAG5D,MAAO,CAAC7Q,EAAAzC,EAAA0C,cAACoR,GAAD,CACNnV,IAAI,UACJ6G,KAAMrE,KAAKuD,MAAMc,KACjB2N,WAAYhS,KAAKuD,MAAMyO,WACvBzG,WAAYvL,KAAKuD,MAAMgI,WACvB0G,aAAcjS,KAAKuD,MAAM0O,aACzBC,sBAAuBlS,KAAKuD,MAAM2O,sBAClClG,cAAehM,KAAKuD,MAAMyI,cAC1BC,gBAAiBjM,KAAKuD,MAAM0I,gBAC5BmG,aAAcpS,KAAK0S,eACnBP,SAAUnS,KAAKoD,MAAM+O,WAEvBnS,KAAKoD,MAAM+O,UAAY7Q,EAAAzC,EAAA0C,cAACqR,GAAD,CACrBpV,IAAI,SACJ6G,KAAMrE,KAAKuD,MAAMc,KACjB8N,SAAUnS,KAAKoD,MAAM+O,SACrBC,aAAcpS,KAAK0S,yBAtBApO,aChDnBoG,GAAa,SAAArJ,GAAA,IAAE2I,EAAF3I,EAAE2I,QAAF,OACjB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMhH,OAAP,KACErC,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMC,IAAP,KACEtJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,cACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,aACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,sBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,qBACAvJ,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAME,WAAP,eAEEvJ,EAAAzC,EAAA0C,cAAC8E,GAAD,KAAW/E,EAAAzC,EAAA0C,cAACK,EAAA,EAAD,CAAQyI,KAAK,OAAO1I,OAAQqI,EAASM,QAAM,SAMxDmC,GAAW,SAAAjE,GAAA,IAAEmE,EAAFnE,EAAEmE,MAAO3C,EAATxB,EAASwB,QAAT,OACf1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAD,CAAOkC,QAAM,GACXvL,EAAAzC,EAAA0C,cAACsR,GAAD,CAAY7I,QAASA,IACrB1I,EAAAzC,EAAA0C,cAACoJ,GAAA,EAAMoC,KAAP,KAEKJ,EAASA,EAAMxE,IAAI,SAAC1K,GAAD,OAClB6D,EAAAzC,EAAA0C,cAACuR,GAAD,CACEtV,IAAKC,EAAK4G,KACVA,KAAM5G,EAAK4G,KACX2N,WAAYvU,EAAKsV,YACjBxH,WAAY9N,EAAKwP,YACjBgF,aAAcxU,EAAKuV,cACnBd,sBAAuBzU,EAAKwV,yBAC5BjH,eAWiB3H,EAXkB5G,EAAK4G,KAYlD,qCAAAzG,OAA4CyG,IAXlC4H,gBAAiByB,GAAsBjQ,EAAK4G,QAUxD,IAA6BA,IARjB/C,EAAAzC,EAAA0C,cAAA,wBAMNoM,GAAY,kBAAOrM,EAAAzC,EAAA0C,cAAA,0CAMzB,SAASmM,GAAsBrJ,GAC7B,6CAAAzG,OAA8CyG,OAG1C6O,8MACJ9P,MAAQ,CACNuJ,WAAOjI,EACPjF,WAAOiF,EACPsF,SAAS,KAOXmJ,oCAAiB,SAAApU,IAAA,IAAAC,EAAA,OAAAJ,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAEbiE,EAAKS,SAAS,CACZiG,SAAS,IAHE5K,EAAAE,KAAA,EAKQhB,EAAI4B,IAAI,gBALhB,OAKPlB,EALOI,EAAAI,KAMb8D,EAAKS,SAAS,CACZ4I,MAAO3N,EAAOoU,SACdpJ,SAAS,EACTvK,WAAOiF,IATItF,EAAAE,KAAA,mBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAYbkE,EAAKS,SAAS,CACZtE,MAAOL,EAAAiG,GAAEnG,QACT8K,SAAS,KAEP5L,EAAcgB,EAAAiG,IAhBL,CAAAjG,EAAAE,KAAA,gBAiBXgE,EAAKC,MAAMyB,WAjBA5F,EAAAQ,OAAA,kBAqBf0D,EAAKC,MAAM4G,WAAW7G,EAAK6P,eAAgB,KArB5B,yBAAA/T,EAAAS,SAAAd,EAAA,oGAHfiB,KAAKmT,kDA4BL,IAAMxG,EAAQ3M,KAAKoD,MAAMuJ,MACzB,OAAKA,EAGgB,IAAjBA,EAAMoB,OACAzM,EAAAzC,EAAA0C,cAAC8R,GAAD,MAEF/R,EAAAzC,EAAA0C,cAAC+R,GAAD,CAAU3G,MAAOA,EAAO3C,QAAShK,KAAKoD,MAAM4G,UAL1C1I,EAAAzC,EAAA0C,cAACiE,EAAD,aAtCYlB,aA+CXmG,OAAa7F,EAASsO,KCjG/BzG,GAAW,kBACfnL,EAAAzC,EAAA0C,cAAA,WACED,EAAAzC,EAAA0C,cAACoC,EAAA,EAAD,CAAQuK,UAAQ,GAAhB,gCAGA5M,EAAAzC,EAAA0C,cAACgS,GAAD,QAsCWC,oLAjCJ,IAAAlQ,EAAAtD,KACP,OACAsB,EAAAzC,EAAA0C,cAAA,WACID,EAAAzC,EAAA0C,cAACuD,EAAcC,SAAf,KACC,SAAAC,GAAQ,OACP1D,EAAAzC,EAAA0C,cAACgF,GAAD,CAAYmI,MAAM,OAAOC,UAAQ,GAC/BrN,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACErH,EAAAzC,EAAA0C,cAACiF,EAAA,EAAK7C,OAAN,4BACArC,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKA,KAAN,KACElF,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,uBAGArH,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,CAAWiG,SAAS,QAAQzK,QAAS,kBAAMa,MAA3C,WAID1B,EAAKC,MAAMsL,oBAKlBvN,EAAAzC,EAAA0C,cAACmF,GAAD,KACApF,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,EAAC1H,UAAQ,GACnB9F,EAAAzC,EAAA0C,cAACkS,GAAD,OAEHzT,KAAKuD,MAAM4L,YACVnP,KAAKuD,MAAM9D,MAAS6B,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,GAACxN,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,CAASxD,OAAK,EAACyD,QAASlD,KAAKuD,MAAM9D,SAAqB,cA3BvE6E,aCJxBoP,GAAc,SAAArS,GAAA,IAAEsS,EAAFtS,EAAEsS,IAAK3L,EAAP3G,EAAO2G,UAAP,OAClB1G,EAAAzC,EAAA0C,cAACC,EAAA,EAAD,CAASsN,OAAK,GACZxN,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAD,KACE3B,EAAAzC,EAAA0C,cAAC0B,EAAA,EAAQU,OAAT,sBACArC,EAAAzC,EAAA0C,cAAA,2BAAkBD,EAAAzC,EAAA0C,cAAA,SAAIoS,GAAtB,iBAA4CrS,EAAAzC,EAAA0C,cAAA,SAAIyG,GAAhD,QAKA4L,GAAgB,SAAApL,GAAyF,IAAvF/I,EAAuF+I,EAAvF/I,MAAOyK,EAAgF1B,EAAhF0B,WAAY2J,EAAoErL,EAApEqL,sBAAuBC,EAA6CtL,EAA7CsL,QAASH,EAAoCnL,EAApCmL,IAAK3L,EAA+BQ,EAA/BR,UAC1E6G,EADyGrG,EAApBuL,eACpD5L,IAAI,SAAC1K,GAAD,OAAU6D,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KAAWrH,EAAAzC,EAAA0C,cAAA,KAAG0J,KAAMxN,EAAKuN,KAAMgJ,GAAkBvW,EAAKmF,UACrGiM,EAAgBd,OAAS,IAC3Bc,EAAmBvN,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKmC,KAAN,KACjBrH,EAAAzC,EAAA0C,cAACiF,EAAA,EAAK7C,OAAN,wBACArC,EAAAzC,EAAA0C,cAACiF,EAAA,EAAKA,KAAN,KAAYqI,KAGhB,IAAIoF,EAAW7C,GAOf,OANIlH,EACF+J,EAAWzF,GACJqF,EACPI,EAAW9C,GACJ2C,IACPG,EAAWT,IAEXlS,EAAAzC,EAAA0C,cAAC0S,EAAD,CACE9E,YAAa7N,EAAAzC,EAAA0C,cAAC2S,GAAD,CAAaP,IAAKA,EAAK3L,UAAWA,IAC/C6G,gBAAiBA,EACjBpP,MAAOA,KAKPuU,GAAoB,SAACG,GACzB,OAAQA,GACN,IAAK,aACH,MAAO,cACT,IAAK,yBACH,MAAO,0BACT,IAAK,UACH,MAAO,UACT,QACE,MAAO,KAIPC,GAAc,kBAClB9S,EAAAzC,EAAA0C,cAACc,EAAA,EAAD,KACEf,EAAAzC,EAAA0C,cAACiE,EAAD,QAIE6O,8MACJjR,MAAQ,CACNkR,eAAW5P,EACXjF,WAAOiF,KAOT6P,qCAAkB,SAAAxV,IAAA,IAAAuV,EAAA,OAAA1V,EAAAC,EAAAM,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAAAD,EAAAE,KAAA,EAEUhB,EAAI4B,IAAI,kBAFlB,OAERoU,EAFQlV,EAAAI,KAGd8D,EAAKS,SAAS,CACZuQ,YACA7U,WAAOiF,IALKtF,EAAAE,KAAA,gBAAAF,EAAAC,KAAA,EAAAD,EAAAiG,GAAAjG,EAAA,SAQdkE,EAAKS,SAAS,CACZtE,MAAOL,EAAAiG,GAAEnG,UAEPd,EAAcgB,EAAAiG,KAChB/B,EAAKC,MAAMyB,WAZC,QAehB1B,EAAKC,MAAM4G,WAAW7G,EAAKiR,gBAAiB,KAf5B,yBAAAnV,EAAAS,SAAAd,EAAA,oGAHhBiB,KAAKuU,mDAsBL,OAAIvU,KAAKoD,MAAMkR,UACNhT,EAAAzC,EAAA0C,cAACiT,GAAD,CACL/U,MAAOO,KAAKoD,MAAM3D,MAClByK,WAAYlK,KAAKoD,MAAMkR,UAAUpK,WACjC2J,sBAAuB7T,KAAKoD,MAAMkR,UAAUG,uBAC5CX,QAAS9T,KAAKoD,MAAMkR,UAAUR,QAC9BC,eAAgB/T,KAAKoD,MAAMkR,UAAUI,OAAS,GAC9Cf,IAAK3T,KAAKoD,MAAMkR,UAAUX,IAC1B3L,UAAWhI,KAAKoD,MAAMkR,UAAUtM,YAG5B1G,EAAAzC,EAAA0C,cAACoT,GAAD,aAxCMrQ,aA4CHmG,OAAa7F,EAASyP,KClG/BO,GAAcC,QACW,cAA7BC,OAAOC,SAASC,UAEe,UAA7BF,OAAOC,SAASC,UAEhBF,OAAOC,SAASC,SAAS3G,MACvB,2DAsCN,SAAS4G,GAAgBC,GACvBC,UAAUC,cACPC,SAASH,GACTI,KAAK,SAAAC,GACJA,EAAaC,cAAgB,WAC3B,IAAMC,EAAmBF,EAAaG,WACtCD,EAAiBE,cAAgB,WACA,cAA3BF,EAAiBrS,QACf+R,UAAUC,cAAcQ,WAK1BC,QAAQC,IAAI,6CAKZD,QAAQC,IAAI,2CAjBxB,MAuBS,SAAArW,GACLoW,QAAQpW,MAAM,4CAA6CA,KCxEjEsW,IAASC,OAAO1U,EAAAzC,EAAA0C,cAAC0U,EAAD,KAAM3U,EAAAzC,EAAA0C,cAAC2U,GAAD,OAAgBC,SAASC,eAAe,SDa/C,WACb,GAA6C,kBAAmBjB,UAAW,CAGzE,GADkB,IAAIkB,IAAIC,GAAwBxB,OAAOC,UAC3CwB,SAAWzB,OAAOC,SAASwB,OAIvC,OAGFzB,OAAO0B,iBAAiB,OAAQ,WAC9B,IAAMtB,EAAK,GAAAtX,OAAM0Y,GAAN,sBAEP1B,IAiDV,SAAiCM,GAE/BzU,MAAMyU,GACHI,KAAK,SAAAmB,GAGkB,MAApBA,EAASpY,SACuD,IAAhEoY,EAASnW,QAAQJ,IAAI,gBAAgBwW,QAAQ,cAG7CvB,UAAUC,cAActM,MAAMwM,KAAK,SAAAC,GACjCA,EAAaoB,aAAarB,KAAK,WAC7BR,OAAOC,SAAS6B,aAKpB3B,GAAgBC,KAftB,MAkBS,WACLW,QAAQC,IACN,mEArEAe,CAAwB3B,GAIxBC,UAAUC,cAActM,MAAMwM,KAAK,WACjCO,QAAQC,IACN,+GAMJb,GAAgBC,MCxCxB4B\",\"file\":\"static/js/main.2a3c7bcb.chunk.js\",\"sourcesContent\":[\"const PREFIX = \\\"kube-arangodb:v1:\\\";\\n\\nexport function getSessionItem(key) {\\n const item = sessionStorage.getItem(`${PREFIX}${key}`);\\n if (item) {\\n try {\\n return JSON.parse(item);\\n } catch (e) {}\\n }\\n return undefined;\\n}\\n\\nexport function setSessionItem(key, value) {\\n sessionStorage.setItem(`${PREFIX}${key}`, JSON.stringify(value));\\n return value;\\n}\\n\",\"export function isUnauthorized(e) { \\n return (e.status === 401);\\n}\\n\\nexport default {\\n token: '',\\n\\n async decodeResults(result) {\\n const decoded = await result.json();\\n if (result.status !== 200) {\\n let message = decoded.error;\\n if (!message) {\\n if (result.status === 401) {\\n message = \\\"Unauthorized\\\";\\n } else {\\n message = `Unexpected status ${result.status}`;\\n }\\n }\\n throw Object.assign(new Error(message), { status: result.status });\\n }\\n return decoded;\\n },\\n \\n // apiGet performs a GET request on the API with given local URL.\\n // The result is decoded from JSON and returned.\\n async get(localURL) {\\n let headers = {\\n 'Accept': 'application/json'\\n };\\n if (this.token) {\\n headers['Authorization'] = `bearer ${this.token}`; \\n }\\n const result = await fetch(localURL, {headers});\\n return this.decodeResults(result);\\n },\\n \\n // apiPost performs a POST request on the API with given local URL and given data.\\n // The result is decoded from JSON and returned.\\n async post(localURL, body) {\\n let headers = {\\n 'Accept': 'application/json',\\n 'Content-Type': 'application/json'\\n };\\n if (this.token) {\\n headers['Authorization'] = `bearer ${this.token}`; \\n }\\n const result = await fetch(localURL, {\\n method: 'POST',\\n headers,\\n body: JSON.stringify(body)\\n });\\n return this.decodeResults(result);\\n }\\n};\\n\",\"import { Dimmer, Loader, Segment } from 'semantic-ui-react';\\nimport React from 'react';\\n\\nconst Loading = ({message}) => (\\n \\n \\n {message || \\\"Loading...\\\"}\\n \\n
\\n \\n );\\n\\nexport default Loading;\\n\",\"import { Button, Container, Form, Icon, Message, Modal } from 'semantic-ui-react';\\nimport { css } from 'react-emotion';\\nimport React, { Component } from 'react';\\n\\nconst LoginView = ({username, password, onUsernameChanged, onPasswordChanged, doLogin, error}) => (\\n \\n
\\n \\n \\n onUsernameChanged(e.target.value)}/>\\n \\n \\n \\n onPasswordChanged(e.target.value)}/>\\n \\n \\n \\n {(error) ? : null}\\n
\\n);\\n\\nclass Login extends Component {\\n state = {\\n username: '',\\n password: ''\\n };\\n\\n handleLogin = () => {\\n this.props.doLogin(this.state.username, this.state.password);\\n }\\n\\n render() {\\n return (\\n \\n Login\\n \\n this.setState({username:v})}\\n onPasswordChanged={(v) => this.setState({password:v})}\\n doLogin={this.handleLogin}\\n />\\n \\n \\n \\n \\n \\n );\\n }\\n}\\n\\nexport default Login;\\n\",\"import React from 'react';\\n\\nconst LogoutContext = React.createContext(undefined);\\n\\nexport default LogoutContext;\\n\\n\",\"import React, { Component } from 'react';\\n\\nimport { getSessionItem, setSessionItem } from '../util/Storage';\\nimport api from '../api/api';\\nimport Loading from '../util/Loading';\\nimport Login from './Login';\\nimport LogoutContext from './LogoutContext';\\n\\nconst tokenSessionKey = \\\"auth-token\\\";\\n\\n// withAuth adds a doLogout property to the given component.\\nexport function withAuth(WrappedComponent) {\\n return function AuthAwareComponent(props) {\\n return (\\n \\n {doLogout => }\\n \\n );\\n }\\n}\\n\\nclass Auth extends Component {\\n state = {\\n authenticated: false,\\n showLoading: true,\\n token: getSessionItem(tokenSessionKey) || \\\"\\\"\\n };\\n\\n async componentDidMount() {\\n try {\\n api.token = this.state.token;\\n await api.get('/api/operators');\\n this.setState({\\n authenticated: true,\\n showLoading: false,\\n token: api.token\\n });\\n } catch (e) {\\n this.setState({\\n authenticated: false,\\n showLoading: false,\\n token: ''\\n });\\n }\\n }\\n\\n handleLogin = async (username, password) => {\\n try {\\n this.setState({error:undefined});\\n const res = await api.post('/login', { username, password });\\n api.token = res.token;\\n setSessionItem(tokenSessionKey, res.token);\\n this.setState({\\n authenticated: true,\\n token: res.token\\n });\\n return true;\\n } catch (e) {\\n this.setState({\\n authenticated: false,\\n token: '',\\n error: e.message\\n });\\n return false;\\n }\\n };\\n\\n handleLogout = () => {\\n api.token = '';\\n setSessionItem(tokenSessionKey, '');\\n this.setState({\\n authenticated: false,\\n token: '',\\n error: undefined\\n });\\n };\\n\\n componentWillUnmount() {\\n }\\n\\n render() {\\n return (\\n \\n {(this.state.showLoading) ? : \\n (!this.state.authenticated) ? \\n :\\n this.props.children\\n }\\n \\n );\\n }\\n}\\n\\nexport default Auth;\\n\",\"import styled from 'react-emotion';\\nimport { Menu } from 'semantic-ui-react';\\n\\nexport const Field = styled('div')`\\n padding-top: 0.3em;\\n padding-bottom: 0.3em;\\n clear: both;\\n`;\\n\\nexport const FieldLabel = styled('span')`\\n width: 9rem;\\n display: inline-block;\\n float: left;\\n`;\\n\\nexport const FieldContent = styled('div')`\\n display: inline-block;\\n`;\\n\\nexport const FieldIcons = styled('div')`\\n float: right;\\n`;\\n\\nexport const LoaderBox = styled('span')`\\n float: right;\\n width: 0;\\n padding-right: 1em;\\n margin-right: 1em;\\n margin-top: 1em;\\n max-width: 0;\\n display: inline-block;\\n`;\\n\\nexport const LoaderBoxForTable = styled('span')`\\n float: right;\\n width: 0;\\n padding-right: 1em;\\n max-width: 0;\\n display: inline-block;\\n`;\\n\\nexport const StyledMenu = styled(Menu)`\\n width: 15rem !important;\\n @media (max-width: 768px) {\\n width: 10rem !important;\\n }\\n`;\\n\\nexport const StyledContentBox = styled('div')`\\n margin-left: 15rem;\\n @media (max-width: 768px) {\\n margin-left: 10rem;\\n }\\n`;\\n\",\"import { Button, Modal, Segment } from 'semantic-ui-react';\\nimport { CopyToClipboard } from 'react-copy-to-clipboard';\\nimport React, { Component } from 'react';\\n\\nclass CommandInstruction extends Component {\\n state = {open:false};\\n\\n close = () => { this.setState({open:false}); }\\n open = () => { this.setState({open:true}); }\\n\\n render() {\\n return (\\n \\n {this.props.title}\\n \\n \\n

\\n {this.props.description}\\n

\\n \\n {this.props.command}\\n \\n
\\n
\\n \\n \\n \\n \\n \\n
\\n );\\n }\\n}\\n\\nexport default CommandInstruction;\\n\",\"import { Accordion, Header, Icon, List, Segment } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\n\\nimport { Field, FieldLabel, FieldIcons } from '../style/style';\\nimport CommandInstruction from '../util/CommandInstruction';\\n\\nconst MemberListView = ({group, activeMemberID, onClick, members, namespace}) => (\\n \\n
{group}
\\n \\n {members.map((item) => )}\\n \\n
\\n);\\n\\nconst MemberOfClusterView = (memberOfCluster) => {\\n if (memberOfCluster === 'never') {\\n return null;\\n }\\n return (\\n \\n Cluster member: \\n {(memberOfCluster ==='true') ? \\\"Yes\\\" : \\\"No\\\"}\\n \\n );\\n}\\n\\nconst MemberView = ({memberInfo, namespace, active, onClick}) => (\\n \\n \\n onClick(memberInfo.id)}>\\n {memberInfo.id} \\n \\n \\n {MemberOfClusterView(memberInfo.member_of_cluster)}\\n \\n Pod: \\n {memberInfo.pod_name || \\\"-\\\"}\\n {(memberInfo.pod_name) ?\\n \\n }\\n command={createLogPodCommand(memberInfo.pod_name, namespace)}\\n title=\\\"Get Pod Logs\\\"\\n description=\\\"To get the log output of this pod, run:\\\"\\n />\\n }\\n command={createDescribePodCommand(memberInfo.pod_name, namespace)}\\n title=\\\"Describe Pod Information\\\"\\n description=\\\"To get more information on the state of this pod, run:\\\"\\n />\\n \\n : null}\\n \\n \\n PVC: \\n {memberInfo.pvc_name || \\\"-\\\"}\\n \\n {(memberInfo.pvc_name) ?\\n }\\n command={createDescribePvcCommand(memberInfo.pvc_name, namespace)}\\n title=\\\"Describe PersistentVolumeClaim Information\\\"\\n description=\\\"To get more information on the state of this PVC, run:\\\"\\n />\\n : null}\\n \\n \\n \\n PV:\\n {memberInfo.pv_name || \\\"-\\\"}\\n \\n {(memberInfo.pv_name) ?\\n }\\n command={createDescribePvCommand(memberInfo.pv_name)}\\n title=\\\"Describe PersistentVolume Information\\\"\\n description=\\\"To get more information on the state of this PV, run:\\\"\\n />\\n : null}\\n \\n \\n \\n \\n \\n);\\n\\nfunction createDescribePodCommand(podName, namespace) {\\n return `kubectl describe pod -n ${namespace} ${podName}`;\\n}\\n\\nfunction createLogPodCommand(podName, namespace) {\\n return `kubectl logs -n ${namespace} ${podName}`;\\n}\\n\\nfunction createDescribePvcCommand(pvcName, namespace) {\\n return `kubectl describe pvc -n ${namespace} ${pvcName}`;\\n}\\n\\nfunction createDescribePvCommand(pvName) {\\n return `kubectl describe pv ${pvName}`;\\n}\\n\\nclass MemberList extends Component {\\n state = {};\\n\\n onClick = (id) => { \\n this.setState({activeMemberID:(this.state.activeMemberID === id) ? null : id}); \\n }\\n\\n render() {\\n return ();\\n }\\n}\\n\\nexport default MemberList;\\n\",\"import { Loader } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth.js';\\nimport api, { isUnauthorized } from '../api/api';\\nimport Loading from '../util/Loading';\\nimport MemberList from './MemberList';\\n\\nconst MemberGroupsView = ({memberGroups, namespace}) => (\\n
\\n {memberGroups.map((item) => )}\\n
\\n);\\n\\nclass DeploymentDetails extends Component {\\n state = {\\n loading: true,\\n error: undefined\\n };\\n\\n componentDidMount() {\\n this.reloadDeployment();\\n }\\n\\n reloadDeployment = async() => {\\n try {\\n this.setState({\\n loading: true\\n });\\n const result = await api.get(`/api/deployment/${this.props.name}`);\\n this.setState({\\n deployment: result,\\n loading: false,\\n error: undefined\\n });\\n } catch (e) {\\n this.setState({\\n loading: false,\\n error: e.message\\n });\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadDeployment, 5000);\\n }\\n\\n render() {\\n const d = this.state.deployment;\\n if (!d) {\\n return ();\\n }\\n return (\\n
\\n \\n \\n
\\n );\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(DeploymentDetails));\\n\",\"import { Icon, Loader, Popup, Table } from 'semantic-ui-react';\\nimport { Link } from \\\"react-router-dom\\\";\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { LoaderBoxForTable as LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth';\\nimport api, { isUnauthorized } from '../api/api';\\nimport CommandInstruction from '../util/CommandInstruction';\\nimport Loading from '../util/Loading';\\n\\nconst HeaderView = ({loading}) => (\\n \\n \\n State\\n Name\\n Mode\\n Version\\n Pods}>Ready / Total\\n Volumes}>Bound / Total\\n StorageClass\\n \\n Actions\\n \\n \\n \\n \\n);\\n\\nconst DatabaseLinkView = ({name, url}) => (\\n \\n }>\\n Go the the web-UI of the database.\\n \\n \\n);\\n\\nconst NoDatabaseLinkView = () => (\\n }>\\n This deployment is not reachable outside the Kubernetes cluster.\\n \\n);\\n\\nconst RowView = ({name, mode, environment, stateColor, version, license, readyPodCount, podCount, readyVolumeCount, volumeCount, storageClasses, databaseURL, deleteCommand, describeCommand}) => (\\n \\n \\n }>\\n {getStateColorDescription(stateColor)}\\n \\n \\n \\n \\n {name}\\n \\n \\n \\n {mode}\\n \\n {(environment===\\\"Development\\\") ? } content=\\\"Development environment\\\"/>: null}\\n {(environment===\\\"Production\\\") ? } content=\\\"Production environment\\\"/>: null}\\n \\n \\n \\n {version}\\n \\n {(license===\\\"community\\\") ? } content=\\\"Community edition\\\"/>: null}\\n {(license===\\\"enterprise\\\") ? } content=\\\"Enterprise edition\\\"/>: null}\\n \\n \\n {readyPodCount} / {podCount}\\n {readyVolumeCount} / {volumeCount}\\n {storageClasses.map((item) => (item === \\\"\\\") ? \\\"\\\" : item)}\\n \\n { databaseURL ? : }\\n }\\n command={describeCommand}\\n title=\\\"Describe deployment\\\"\\n description=\\\"To get more information on the state of this deployment, run:\\\"\\n />\\n \\n }\\n command={deleteCommand}\\n title=\\\"Delete deployment\\\"\\n description=\\\"To delete this deployment, run:\\\"\\n />\\n \\n \\n \\n);\\n\\nconst ListView = ({items, loading}) => (\\n \\n \\n \\n {\\n (items) ? items.map((item) => \\n ) :

No items

\\n }\\n
\\n
\\n);\\n\\nconst EmptyView = () => (
No deployments
);\\n\\nfunction createDeleteCommand(name, namespace) {\\n return `kubectl delete ArangoDeployment -n ${namespace} ${name}`;\\n}\\n\\nfunction createDescribeCommand(name, namespace) {\\n return `kubectl describe ArangoDeployment -n ${namespace} ${name}`;\\n}\\n\\nfunction getStateColorDescription(stateColor) {\\n switch (stateColor) {\\n case \\\"green\\\":\\n return \\\"Everything is running smooth.\\\";\\n case \\\"yellow\\\":\\n return \\\"There is some activity going on, but deployment is available.\\\";\\n case \\\"orange\\\":\\n return \\\"There is some activity going on, deployment may be/become unavailable. You should pay attention now!\\\";\\n case \\\"red\\\":\\n return \\\"The deployment is in a bad state and manual intervention is likely needed.\\\";\\n default:\\n return \\\"State is not known.\\\";\\n }\\n}\\n\\nclass DeploymentList extends Component {\\n state = {\\n items: null,\\n error: null,\\n loading: true\\n };\\n\\n componentDidMount() {\\n this.reloadDeployments();\\n }\\n\\n reloadDeployments = async() => {\\n try {\\n this.setState({loading: true});\\n const result = await api.get('/api/deployment');\\n this.setState({\\n items: result.deployments,\\n loading: false,\\n error: null\\n });\\n } catch (e) {\\n this.setState({error: e.message, loading: false});\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadDeployments, 5000);\\n }\\n\\n render() {\\n const items = this.state.items;\\n if (!items) {\\n return ();\\n }\\n if (items.length === 0) {\\n return ();\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(DeploymentList));\\n\",\"import { BrowserRouter as Router, Route, Link } from \\\"react-router-dom\\\";\\nimport { Header, Menu, Message, Segment } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\n\\nimport { StyledMenu, StyledContentBox } from '../style/style';\\nimport DeploymentDetails from './DeploymentDetails';\\nimport DeploymentList from './DeploymentList';\\nimport LogoutContext from '../auth/LogoutContext';\\n\\nconst ListView = () => (\\n
\\n
\\n ArangoDeployment resources\\n
\\n \\n
\\n);\\n\\nconst DetailView = ({match}) => (\\n
\\n
\\n ArangoDeployment {match.params.name}\\n
\\n \\n
\\n);\\n\\nclass DeploymentOperator extends Component {\\n render() {\\n return (\\n \\n
\\n \\n {doLogout => \\n \\n \\n Deployment Operator\\n \\n \\n Deployments\\n \\n doLogout()}>\\n Logout\\n \\n \\n {this.props.commonMenuItems}\\n \\n \\n }\\n \\n \\n \\n
\\n \\n \\n
\\n
\\n {this.props.podInfoView}\\n {(this.props.error) ? : null}\\n
\\n
\\n
\\n );\\n }\\n}\\n\\nexport default DeploymentOperator;\\n\",\"import { Header, Loader, Segment } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { Field, FieldContent as FC, FieldLabel as FL } from '../style/style';\\nimport { LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth';\\nimport api, { isUnauthorized } from '../api/api';\\nimport Loading from '../util/Loading';\\n\\nconst EndpointView = ({title, deploymentName, masterEndpoint, authKeyfileSecretName, authUserSecretName, tlsCACert, tlsCACertSecretName}) => (\\n \\n
{title}
\\n \\n Deployment\\n {deploymentName || \\\"-\\\"}\\n \\n \\n Master endpoint\\n {masterEndpoint || \\\"-\\\"}\\n \\n \\n TLS CA Certificate\\n {tlsCACert}\\n \\n
Secret names
\\n \\n Authentication keyfile\\n {authKeyfileSecretName || \\\"-\\\"}\\n \\n \\n Authentication user\\n {authUserSecretName || \\\"-\\\"}\\n \\n \\n TLS CA Certificate\\n {tlsCACertSecretName || \\\"-\\\"}\\n \\n
\\n);\\n\\nconst DetailsView = ({replication, loading}) => (\\n
\\n \\n \\n \\n
\\n);\\n\\nclass DeploymentReplicationDetails extends Component {\\n state = {\\n loading: true,\\n error: undefined\\n };\\n\\n componentDidMount() {\\n this.reloadDeploymentReplications();\\n }\\n\\n reloadDeploymentReplications = async() => {\\n try {\\n this.setState({\\n loading: true\\n });\\n const result = await api.get(`/api/deployment-replication/${this.props.name}`);\\n this.setState({\\n replication: result,\\n loading: false,\\n error: undefined\\n });\\n } catch (e) {\\n this.setState({\\n loading: false,\\n error: e.message\\n });\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadDeploymentReplications, 5000);\\n }\\n\\n render() {\\n const dr = this.state.replication;\\n if (!dr) {\\n return ();\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(DeploymentReplicationDetails));\\n\",\"import { Icon, Loader, Popup, Table } from 'semantic-ui-react';\\nimport { Link } from \\\"react-router-dom\\\";\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { LoaderBoxForTable as LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth';\\nimport api, { isUnauthorized } from '../api/api';\\nimport CommandInstruction from '../util/CommandInstruction';\\nimport Loading from '../util/Loading';\\n\\nconst HeaderView = ({loading}) => (\\n \\n \\n State\\n Name\\n Source\\n Destination\\n \\n Actions\\n \\n \\n \\n \\n);\\n\\nconst RowView = ({name, mode, stateColor, source, destination, deleteCommand, describeCommand}) => (\\n \\n \\n }>\\n {getStateColorDescription(stateColor)}\\n \\n \\n \\n \\n {name}\\n \\n \\n \\n {source}\\n \\n \\n {destination}\\n \\n \\n }\\n command={describeCommand}\\n title=\\\"Describe deployment replication\\\"\\n description=\\\"To get more information on the state of this deployment replication, run:\\\"\\n />\\n \\n }\\n command={deleteCommand}\\n title=\\\"Delete deployment replication\\\"\\n description=\\\"To delete this deployment replication, run:\\\"\\n />\\n \\n \\n \\n);\\n\\nconst ListView = ({items, loading}) => (\\n \\n \\n \\n {\\n (items) ? items.map((item) => \\n ) :

No items

\\n }\\n
\\n
\\n);\\n\\nconst EmptyView = () => (
No deployment replications
);\\n\\nfunction createDeleteCommand(name, namespace) {\\n return `kubectl delete ArangoDeploymentReplication -n ${namespace} ${name}`;\\n}\\n\\nfunction createDescribeCommand(name, namespace) {\\n return `kubectl describe ArangoDeploymentReplication -n ${namespace} ${name}`;\\n}\\n\\nfunction getStateColorDescription(stateColor) {\\n switch (stateColor) {\\n case \\\"green\\\":\\n return \\\"Replication has been configured.\\\";\\n case \\\"yellow\\\":\\n return \\\"Replication is being configured.\\\";\\n case \\\"red\\\":\\n return \\\"The replication is in a bad state and manual intervention is likely needed.\\\";\\n default:\\n return \\\"State is not known.\\\";\\n }\\n}\\n\\nclass DeploymentReplicationList extends Component {\\n state = {\\n items: null,\\n error: null,\\n loading: true\\n };\\n\\n componentDidMount() {\\n this.reloadDeploymentReplications();\\n }\\n\\n reloadDeploymentReplications = async() => {\\n try {\\n this.setState({loading: true});\\n const result = await api.get('/api/deployment-replication');\\n this.setState({\\n items: result.replications,\\n loading: false,\\n error: null\\n });\\n } catch (e) {\\n this.setState({error: e.message, loading: false});\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadDeploymentReplications, 5000);\\n }\\n\\n render() {\\n const items = this.state.items;\\n if (!items) {\\n return ();\\n }\\n if (items.length === 0) {\\n return ();\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(DeploymentReplicationList));\\n\",\"import { BrowserRouter as Router, Route, Link } from \\\"react-router-dom\\\";\\nimport { Header, Menu, Message, Segment } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\n\\nimport { StyledMenu, StyledContentBox } from '../style/style';\\nimport DeploymentReplicationDetails from './DeploymentReplicationDetails';\\nimport DeploymentReplicationList from './DeploymentReplicationList';\\nimport LogoutContext from '../auth/LogoutContext';\\n\\nconst ListView = () => (\\n
\\n
\\n ArangoDeploymentReplication resources\\n
\\n \\n
\\n);\\n\\nconst DetailView = ({match}) => (\\n
\\n
\\n ArangoDeploymentReplication {match.params.name}\\n
\\n \\n
\\n);\\n\\nclass DeploymentReplicationOperator extends Component {\\n render() {\\n return (\\n \\n
\\n \\n {doLogout => \\n \\n \\n Deployment Replication Operator\\n \\n \\n Deployment replications\\n \\n doLogout()}>\\n Logout\\n \\n \\n \\n {this.props.commonMenuItems}\\n \\n }\\n \\n \\n \\n
\\n \\n \\n
\\n
\\n {this.props.podInfoView}\\n {(this.props.error) ? : null}\\n
\\n
\\n
\\n );\\n }\\n}\\n\\nexport default DeploymentReplicationOperator;\\n\",\"import { Container, Message, Modal, Segment } from 'semantic-ui-react';\\nimport React from 'react';\\n\\nconst NoOperator = () => (\\n \\n \\n Welcome to Kube-ArangoDB\\n \\n \\n \\n There are no operators available yet.\\n \\n \\n {this.props.podInfoView}\\n {(this.props.error) ? : null}\\n \\n \\n \\n);\\n\\nexport default NoOperator;\\n\",\"import { Icon, Loader, Popup, Table } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { LoaderBoxForTable as LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth';\\nimport api, { isUnauthorized } from '../api/api';\\nimport CommandInstruction from '../util/CommandInstruction';\\nimport Loading from '../util/Loading';\\n\\nconst HeaderView = ({loading}) => (\\n \\n \\n State\\n Name\\n Capacity\\n Node\\n \\n Actions\\n \\n \\n \\n \\n);\\n\\nconst RowView = ({name, stateColor, nodeName, capacity, describeCommand, deleteCommand}) => (\\n \\n \\n }>\\n {getStateColorDescription(stateColor)}\\n \\n \\n \\n {name}\\n \\n \\n {capacity}\\n \\n \\n {nodeName}\\n \\n \\n }\\n command={describeCommand}\\n title=\\\"Describe PersistentVolume\\\"\\n description=\\\"To get more information on the state of this PersistentVolume, run:\\\"\\n />\\n \\n }\\n command={deleteCommand}\\n title=\\\"Delete PersistentVolume\\\"\\n description=\\\"To delete this PersistentVolume, run:\\\"\\n />\\n \\n \\n \\n);\\n\\nconst ListView = ({items, loading}) => (\\n \\n \\n \\n {\\n (items) ? items.map((item) => \\n \\n ) :

No items

\\n }\\n
\\n
\\n);\\n\\nconst EmptyView = () => (
No PersistentVolumes
);\\n\\nfunction createDeleteCommand(name) {\\n return `kubectl delete PersistentVolume ${name}`;\\n}\\n\\nfunction createDescribeCommand(name) {\\n return `kubectl describe PersistentVolume ${name}`;\\n}\\n\\nfunction getStateColorDescription(stateColor) {\\n switch (stateColor) {\\n case \\\"green\\\":\\n return \\\"Everything is running smooth.\\\";\\n case \\\"yellow\\\":\\n return \\\"There is some activity going on, but PersistentVolume is available.\\\";\\n case \\\"orange\\\":\\n return \\\"There is some activity going on, PersistentVolume may be/become unavailable. You should pay attention now!\\\";\\n case \\\"red\\\":\\n return \\\"The PersistentVolume is in a bad state and manual intervention is likely needed.\\\";\\n default:\\n return \\\"State is not known.\\\";\\n }\\n}\\n\\nclass VolumeList extends Component {\\n state = {\\n items: undefined,\\n error: undefined,\\n loading: true\\n };\\n\\n componentDidMount() {\\n this.reloadVolumes();\\n }\\n\\n reloadVolumes = async() => {\\n try {\\n this.setState({\\n loading: true\\n });\\n const result = await api.get(`/api/storage/${this.props.storageName}`);\\n this.setState({\\n items: result.volumes,\\n loading: false,\\n error: undefined\\n });\\n } catch (e) {\\n this.setState({\\n error: e.message,\\n loading: false\\n });\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadVolumes, 5000);\\n }\\n\\n render() {\\n const items = this.state.items;\\n if (!items) {\\n return ();\\n }\\n if (items.length === 0) {\\n return ();\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(VolumeList));\\n\",\"import { Accordion, Header, Icon, Popup, Table } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\n\\nimport CommandInstruction from '../util/CommandInstruction';\\nimport VolumeList from './VolumeList';\\n\\nconst RowView = ({name, stateColor,localPaths, storageClass, storageClassIsDefault, deleteCommand, describeCommand, expanded, toggleExpand}) => (\\n \\n \\n }>\\n {getStateColorDescription(stateColor)}\\n \\n \\n \\n \\n \\n \\n {name}\\n \\n \\n \\n \\n {localPaths.map((item, index) => {item})}\\n \\n \\n {storageClass}\\n \\n {storageClassIsDefault && } content=\\\"Default storage class\\\"/>}\\n \\n \\n \\n }\\n command={describeCommand}\\n title=\\\"Describe local storage\\\"\\n description=\\\"To get more information on the state of this local storage, run:\\\"\\n />\\n \\n }\\n command={deleteCommand}\\n title=\\\"Delete local storage\\\"\\n description=\\\"To delete this local storage, run:\\\"\\n />\\n \\n \\n \\n);\\n\\nconst VolumesRowView = ({name}) => (\\n \\n \\n
Volumes
\\n \\n
\\n
\\n);\\n\\nclass StorageRow extends Component {\\n state = {expanded: true};\\n\\n onToggleExpand = () => { this.setState({expanded: !this.state.expanded});}\\n\\n render() {\\n return [,\\n this.state.expanded && \\n ];\\n }\\n}\\n\\nfunction getStateColorDescription(stateColor) {\\n switch (stateColor) {\\n case \\\"green\\\":\\n return \\\"Everything is running smooth.\\\";\\n case \\\"yellow\\\":\\n return \\\"There is some activity going on, but local storage is available.\\\";\\n case \\\"orange\\\":\\n return \\\"There is some activity going on, local storage may be/become unavailable. You should pay attention now!\\\";\\n case \\\"red\\\":\\n return \\\"The local storage is in a bad state and manual intervention is likely needed.\\\";\\n default:\\n return \\\"State is not known.\\\";\\n }\\n}\\n\\nexport default StorageRow;\\n\",\"import { Loader, Table } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { LoaderBoxForTable as LoaderBox } from '../style/style';\\nimport { withAuth } from '../auth/Auth';\\nimport api, { isUnauthorized } from '../api/api';\\nimport Loading from '../util/Loading';\\nimport StorageRow from './StorageRow';\\n\\nconst HeaderView = ({loading}) => (\\n \\n \\n State\\n Name\\n Local path(s)\\n StorageClass\\n \\n Actions\\n \\n \\n \\n \\n);\\n\\nconst ListView = ({items, loading}) => (\\n \\n \\n \\n {\\n (items) ? items.map((item) => \\n \\n ) :

No items

\\n }\\n
\\n
\\n);\\n\\nconst EmptyView = () => (
No local storage resources
);\\n\\nfunction createDeleteCommand(name) {\\n return `kubectl delete ArangoLocalStorage ${name}`;\\n}\\n\\nfunction createDescribeCommand(name) {\\n return `kubectl describe ArangoLocalStorage ${name}`;\\n}\\n\\nclass StorageList extends Component {\\n state = {\\n items: undefined,\\n error: undefined,\\n loading: true\\n };\\n\\n componentDidMount() {\\n this.reloadStorages();\\n }\\n\\n reloadStorages = async() => {\\n try {\\n this.setState({\\n loading: true\\n });\\n const result = await api.get('/api/storage');\\n this.setState({\\n items: result.storages,\\n loading: false,\\n error: undefined\\n });\\n } catch (e) {\\n this.setState({\\n error: e.message,\\n loading: false\\n });\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n return;\\n }\\n }\\n this.props.setTimeout(this.reloadStorages, 5000);\\n }\\n\\n render() {\\n const items = this.state.items;\\n if (!items) {\\n return ();\\n }\\n if (items.length === 0) {\\n return ();\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(StorageList));\\n\",\"import { Header, Menu, Message, Segment } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\n\\nimport { StyledMenu, StyledContentBox } from '../style/style';\\nimport LogoutContext from '../auth/LogoutContext';\\nimport StorageList from './StorageList';\\n\\nconst ListView = () => (\\n
\\n
\\n ArangoLocalStorage resources\\n
\\n \\n
\\n);\\n\\nclass StorageOperator extends Component {\\n render() {\\n return (\\n
\\n \\n {doLogout => \\n \\n \\n Deployment Operator\\n \\n \\n Local storages\\n \\n doLogout()}>\\n Logout\\n \\n \\n {this.props.commonMenuItems}\\n \\n \\n }\\n \\n \\n \\n \\n \\n {this.props.podInfoView}\\n {(this.props.error) ? : null}\\n \\n
\\n );\\n }\\n}\\n\\nexport default StorageOperator;\\n\",\"import { Container, Segment, Menu, Message } from 'semantic-ui-react';\\nimport React, { Component } from 'react';\\nimport ReactTimeout from 'react-timeout';\\n\\nimport { withAuth } from './auth/Auth';\\nimport api, { isUnauthorized } from './api/api';\\nimport DeploymentOperator from './deployment/DeploymentOperator';\\nimport DeploymentReplicationOperator from './replication/DeploymentReplicationOperator';\\nimport Loading from './util/Loading';\\nimport NoOperator from './NoOperator';\\nimport StorageOperator from './storage/StorageOperator';\\n\\nconst PodInfoView = ({pod, namespace}) => (\\n \\n \\n Kube-ArangoDB\\n

Running in Pod {pod} in namespace {namespace}.

\\n
\\n
\\n);\\n\\nconst OperatorsView = ({error, deployment, deploymentReplication, storage, pod, namespace, otherOperators}) => {\\n let commonMenuItems = otherOperators.map((item) => {operatorType2Name(item.type)});\\n if (commonMenuItems.length > 0) {\\n commonMenuItems = (\\n Other operators\\n {commonMenuItems}\\n );\\n }\\n let Operator = NoOperator;\\n if (deployment)\\n Operator = DeploymentOperator;\\n else if (deploymentReplication) \\n Operator = DeploymentReplicationOperator;\\n else if (storage)\\n Operator = StorageOperator;\\n return (\\n }\\n commonMenuItems={commonMenuItems}\\n error={error}\\n />\\n );\\n}\\n\\nconst operatorType2Name = (oType) => {\\n switch (oType) {\\n case \\\"deployment\\\":\\n return \\\"Deployments\\\";\\n case \\\"deployment_replication\\\":\\n return \\\"Deployment replications\\\";\\n case \\\"storage\\\":\\n return \\\"Storage\\\";\\n default:\\n return \\\"\\\";\\n }\\n};\\n\\nconst LoadingView = () => (\\n \\n \\n \\n);\\n\\nclass App extends Component {\\n state = {\\n operators: undefined,\\n error: undefined\\n };\\n\\n componentDidMount() {\\n this.reloadOperators();\\n }\\n\\n reloadOperators = async() => {\\n try {\\n const operators = await api.get('/api/operators');\\n this.setState({\\n operators,\\n error: undefined\\n });\\n } catch (e) {\\n this.setState({\\n error: e.message\\n });\\n if (isUnauthorized(e)) {\\n this.props.doLogout();\\n }\\n }\\n this.props.setTimeout(this.reloadOperators, 10000);\\n }\\n\\n render() {\\n if (this.state.operators) {\\n return ;\\n }\\n return ();\\n }\\n}\\n\\nexport default ReactTimeout(withAuth(App));\\n\",\"// In production, we register a service worker to serve assets from local cache.\\n\\n// This lets the app load faster on subsequent visits in production, and gives\\n// it offline capabilities. However, it also means that developers (and users)\\n// will only see deployed updates on the \\\"N+1\\\" visit to a page, since previously\\n// cached resources are updated in the background.\\n\\n// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.\\n// This link also includes instructions on opting out of this behavior.\\n\\nconst isLocalhost = Boolean(\\n window.location.hostname === 'localhost' ||\\n // [::1] is the IPv6 localhost address.\\n window.location.hostname === '[::1]' ||\\n // 127.0.0.1/8 is considered localhost for IPv4.\\n window.location.hostname.match(\\n /^127(?:\\\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\\n )\\n);\\n\\nexport default function register() {\\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\\n // The URL constructor is available in all browsers that support SW.\\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location);\\n if (publicUrl.origin !== window.location.origin) {\\n // Our service worker won't work if PUBLIC_URL is on a different origin\\n // from what our page is served on. This might happen if a CDN is used to\\n // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374\\n return;\\n }\\n\\n window.addEventListener('load', () => {\\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\\n\\n if (isLocalhost) {\\n // This is running on localhost. Lets check if a service worker still exists or not.\\n checkValidServiceWorker(swUrl);\\n\\n // Add some additional logging to localhost, pointing developers to the\\n // service worker/PWA documentation.\\n navigator.serviceWorker.ready.then(() => {\\n console.log(\\n 'This web app is being served cache-first by a service ' +\\n 'worker. To learn more, visit https://goo.gl/SC7cgQ'\\n );\\n });\\n } else {\\n // Is not local host. Just register service worker\\n registerValidSW(swUrl);\\n }\\n });\\n }\\n}\\n\\nfunction registerValidSW(swUrl) {\\n navigator.serviceWorker\\n .register(swUrl)\\n .then(registration => {\\n registration.onupdatefound = () => {\\n const installingWorker = registration.installing;\\n installingWorker.onstatechange = () => {\\n if (installingWorker.state === 'installed') {\\n if (navigator.serviceWorker.controller) {\\n // At this point, the old content will have been purged and\\n // the fresh content will have been added to the cache.\\n // It's the perfect time to display a \\\"New content is\\n // available; please refresh.\\\" message in your web app.\\n console.log('New content is available; please refresh.');\\n } else {\\n // At this point, everything has been precached.\\n // It's the perfect time to display a\\n // \\\"Content is cached for offline use.\\\" message.\\n console.log('Content is cached for offline use.');\\n }\\n }\\n };\\n };\\n })\\n .catch(error => {\\n console.error('Error during service worker registration:', error);\\n });\\n}\\n\\nfunction checkValidServiceWorker(swUrl) {\\n // Check if the service worker can be found. If it can't reload the page.\\n fetch(swUrl)\\n .then(response => {\\n // Ensure service worker exists, and that we really are getting a JS file.\\n if (\\n response.status === 404 ||\\n response.headers.get('content-type').indexOf('javascript') === -1\\n ) {\\n // No service worker found. Probably a different app. Reload the page.\\n navigator.serviceWorker.ready.then(registration => {\\n registration.unregister().then(() => {\\n window.location.reload();\\n });\\n });\\n } else {\\n // Service worker found. Proceed as normal.\\n registerValidSW(swUrl);\\n }\\n })\\n .catch(() => {\\n console.log(\\n 'No internet connection found. App is running in offline mode.'\\n );\\n });\\n}\\n\\nexport function unregister() {\\n if ('serviceWorker' in navigator) {\\n navigator.serviceWorker.ready.then(registration => {\\n registration.unregister();\\n });\\n }\\n}\\n\",\"import React from 'react';\\nimport ReactDOM from 'react-dom';\\nimport './index.css';\\nimport App from './App';\\nimport Auth from './auth/Auth.js';\\nimport registerServiceWorker from './registerServiceWorker';\\n\\nReactDOM.render(, document.getElementById('root'));\\nregisterServiceWorker();\\n\"],\"sourceRoot\":\"\"}" -var _Assetsed05d70a6de7e2387f4d468abfa530ef361026b4 = "(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{246:function(e,t,a){e.exports=a(420)},251:function(e,t,a){},420:function(e,t,a){\"use strict\";a.r(t);var n=a(0),r=a.n(n),l=a(60),o=a.n(l),c=(a(251),a(15)),i=a.n(c),s=a(24),u=a(16),m=a(17),d=a(19),p=a(18),E=a(20),g=a(446),h=a(437),f=a(439),v=a(431),b=a(32),y=a.n(b),C=\"kube-arangodb:v1:\";function k(e){var t=sessionStorage.getItem(\"\".concat(C).concat(e));if(t)try{return JSON.parse(t)}catch(a){}}function w(e,t){return sessionStorage.setItem(\"\".concat(C).concat(e),JSON.stringify(t)),t}function O(e){return 401===e.status}var j={token:\"\",decodeResults:function(){var e=Object(s.a)(i.a.mark(function e(t){var a,n;return i.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t.json();case 2:if(a=e.sent,200===t.status){e.next=7;break}throw(n=a.error)||(n=401===t.status?\"Unauthorized\":\"Unexpected status \".concat(t.status)),Object.assign(new Error(n),{status:t.status});case 7:return e.abrupt(\"return\",a);case 8:case\"end\":return e.stop()}},e)}));return function(t){return e.apply(this,arguments)}}(),get:function(){var e=Object(s.a)(i.a.mark(function e(t){var a,n;return i.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return a={Accept:\"application/json\"},this.token&&(a.Authorization=\"bearer \".concat(this.token)),e.next=4,fetch(t,{headers:a});case 4:return n=e.sent,e.abrupt(\"return\",this.decodeResults(n));case 6:case\"end\":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}(),post:function(){var e=Object(s.a)(i.a.mark(function e(t,a){var n,r;return i.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return n={Accept:\"application/json\",\"Content-Type\":\"application/json\"},this.token&&(n.Authorization=\"bearer \".concat(this.token)),e.next=4,fetch(t,{method:\"POST\",headers:n,body:JSON.stringify(a)});case 4:return r=e.sent,e.abrupt(\"return\",this.decodeResults(r));case 6:case\"end\":return e.stop()}},e,this)}));return function(t,a){return e.apply(this,arguments)}}()},S=a(443),x=a(430),_=function(e){var t=e.message;return r.a.createElement(g.a,null,r.a.createElement(S.a,{inverted:!0,active:!0},r.a.createElement(x.a,{inverted:!0},t||\"Loading...\")),r.a.createElement(\"div\",{style:{minHeight:\"3em\"}}))},D=a(36),A=a(433),T=a(434),L=a(438),I=a(53),N=a(37);function H(){var e=Object(D.a)([\"display:none\"]);return H=function(){return e},e}var P=function(e){var t=e.username,a=e.password,n=e.onUsernameChanged,l=e.onPasswordChanged,o=e.doLogin,c=e.error;return r.a.createElement(v.a,null,r.a.createElement(A.a,{onSubmit:o},r.a.createElement(A.a.Field,null,r.a.createElement(\"label\",null,\"Name\"),r.a.createElement(\"input\",{focus:\"true\",value:t,onChange:function(e){return n(e.target.value)}})),r.a.createElement(A.a.Field,null,r.a.createElement(\"label\",null,\"Password\"),r.a.createElement(\"input\",{type:\"password\",value:a,onChange:function(e){return l(e.target.value)}})),r.a.createElement(A.a.Button,{className:Object(N.a)(H()),type:\"submit\"})),c?r.a.createElement(h.a,{error:!0,content:c}):null)},R=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),l=0;l\":e})),r.a.createElement(Ce.a.Cell,null,p?r.a.createElement(Oe,{name:t,url:p}):r.a.createElement(je,null),r.a.createElement(me,{trigger:r.a.createElement(I.a,{link:!0,name:\"zoom\"}),command:g,title:\"Describe deployment\",description:\"To get more information on the state of this deployment, run:\"}),r.a.createElement(\"span\",{style:{float:\"right\"}},r.a.createElement(me,{trigger:r.a.createElement(I.a,{link:!0,name:\"trash\"}),command:E,title:\"Delete deployment\",description:\"To delete this deployment, run:\"}))))},xe=function(e){var t=e.items,a=e.loading;return r.a.createElement(Ce.a,{striped:!0,celled:!0},r.a.createElement(we,{loading:a}),r.a.createElement(Ce.a.Body,null,t?t.map(function(e){return r.a.createElement(Se,{key:e.name,name:e.name,namespace:e.namespace,mode:e.mode,environment:e.environment,stateColor:e.state_color,version:e.database_version,license:e.database_license,readyPodCount:e.ready_pod_count,podCount:e.pod_count,readyVolumeCount:e.ready_volume_count,volumeCount:e.volume_count,storageClasses:e.storage_classes,databaseURL:e.database_url,deleteCommand:(t=e.name,a=e.namespace,\"kubectl delete ArangoDeployment -n \".concat(a,\" \").concat(t)),describeCommand:De(e.name,e.namespace)});var t,a}):r.a.createElement(\"p\",null,\"No items\")))},_e=function(){return r.a.createElement(\"div\",null,\"No deployments\")};function De(e,t){return\"kubectl describe ArangoDeployment -n \".concat(t,\" \").concat(e)}var Ae=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),l=0;l0&&(i=r.a.createElement(f.a.Item,null,r.a.createElement(f.a.Header,null,\"Other operators\"),r.a.createElement(f.a.Menu,null,i)));var s=Qe;return a?s=Ne:n?s=Fe:l&&(s=pt),r.a.createElement(s,{podInfoView:r.a.createElement(Et,{pod:o,namespace:c}),commonMenuItems:i,error:t})},ht=function(e){switch(e){case\"deployment\":return\"Deployments\";case\"deployment_replication\":return\"Deployment replications\";case\"storage\":return\"Storage\";default:return\"\"}},ft=function(){return r.a.createElement(v.a,null,r.a.createElement(_,null))},vt=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),l=0;lKube-ArangoDB
" -var _Assets8485f66015fd3ccc288ef3e6b74600848ca8cc6b = "{\n \"short_name\": \"Kube-ArangoDB Dashboard\",\n \"name\": \"Kube-ArangoDB Operator Dashboard\",\n \"icons\": [\n {\n \"src\": \"favicon.ico\",\n \"sizes\": \"64x64 32x32 24x24 16x16\",\n \"type\": \"image/x-icon\"\n }\n ],\n \"start_url\": \"./index.html\",\n \"display\": \"standalone\",\n \"theme_color\": \"#000000\",\n \"background_color\": \"#ffffff\"\n}\n" -var _Assets977377568b7e5c2c7c98e20c2276663fe7e329aa = "(window.webpackJsonp=window.webpackJsonp||[]).push([[2],[function(e,t,n){\"use strict\";e.exports=n(247)},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}},function(e,t){e.exports=function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t){function n(){return e.exports=n=Object.assign||function(e){for(var t=1;t-1:!!s&&r(e,t,n)>-1}},function(e,t,n){var r=n(57),o=n(41),a=n(342),i=n(21);e.exports=function(e,t){return(i(e)?r:a)(e,o(t,3))}},function(e,t,n){\"use strict\";function r(e,t,n,r,o,a,i){try{var l=e[a](i),c=l.value}catch(u){return void n(u)}l.done?t(c):Promise.resolve(c).then(r,o)}function o(e){return function(){var t=this,n=arguments;return new Promise(function(o,a){var i=e.apply(t,n);function l(e){r(i,o,a,l,c,\"next\",e)}function c(e){r(i,o,a,l,c,\"throw\",e)}l(void 0)})}}n.d(t,\"a\",function(){return o})},function(e,t,n){\"use strict\";for(var r=function(e){return null!==e&&!Array.isArray(e)&&\"object\"===typeof e},o={3:\"Cancel\",6:\"Help\",8:\"Backspace\",9:\"Tab\",12:\"Clear\",13:\"Enter\",16:\"Shift\",17:\"Control\",18:\"Alt\",19:\"Pause\",20:\"CapsLock\",27:\"Escape\",28:\"Convert\",29:\"NonConvert\",30:\"Accept\",31:\"ModeChange\",32:\" \",33:\"PageUp\",34:\"PageDown\",35:\"End\",36:\"Home\",37:\"ArrowLeft\",38:\"ArrowUp\",39:\"ArrowRight\",40:\"ArrowDown\",41:\"Select\",42:\"Print\",43:\"Execute\",44:\"PrintScreen\",45:\"Insert\",46:\"Delete\",48:[\"0\",\")\"],49:[\"1\",\"!\"],50:[\"2\",\"@\"],51:[\"3\",\"#\"],52:[\"4\",\"$\"],53:[\"5\",\"%\"],54:[\"6\",\"^\"],55:[\"7\",\"&\"],56:[\"8\",\"*\"],57:[\"9\",\"(\"],91:\"OS\",93:\"ContextMenu\",144:\"NumLock\",145:\"ScrollLock\",181:\"VolumeMute\",182:\"VolumeDown\",183:\"VolumeUp\",186:[\";\",\":\"],187:[\"=\",\"+\"],188:[\",\",\"<\"],189:[\"-\",\"_\"],190:[\".\",\">\"],191:[\"/\",\"?\"],192:[\"`\",\"~\"],219:[\"[\",\"{\"],220:[\"\\\\\",\"|\"],221:[\"]\",\"}\"],222:[\"'\",'\"'],224:\"Meta\",225:\"AltGraph\",246:\"Attn\",247:\"CrSel\",248:\"ExSel\",249:\"EraseEof\",250:\"Play\",251:\"ZoomOut\"},a=0;a<24;a+=1)o[112+a]=\"F\"+(a+1);for(var i=0;i<26;i+=1){var l=i+65;o[l]=[String.fromCharCode(l+32),String.fromCharCode(l)]}var c={codes:o,getCode:function(e){return r(e)?e.keyCode||e.which||this[e.key]:this[e]},getKey:function(e){var t=r(e);if(t&&e.key)return e.key;var n=o[t?e.keyCode||e.which:e];return Array.isArray(n)&&(n=t?n[e.shiftKey?1:0]:n[0]),n},Cancel:3,Help:6,Backspace:8,Tab:9,Clear:12,Enter:13,Shift:16,Control:17,Alt:18,Pause:19,CapsLock:20,Escape:27,Convert:28,NonConvert:29,Accept:30,ModeChange:31,\" \":32,PageUp:33,PageDown:34,End:35,Home:36,ArrowLeft:37,ArrowUp:38,ArrowRight:39,ArrowDown:40,Select:41,Print:42,Execute:43,PrintScreen:44,Insert:45,Delete:46,0:48,\")\":48,1:49,\"!\":49,2:50,\"@\":50,3:51,\"#\":51,4:52,$:52,5:53,\"%\":53,6:54,\"^\":54,7:55,\"&\":55,8:56,\"*\":56,9:57,\"(\":57,a:65,A:65,b:66,B:66,c:67,C:67,d:68,D:68,e:69,E:69,f:70,F:70,g:71,G:71,h:72,H:72,i:73,I:73,j:74,J:74,k:75,K:75,l:76,L:76,m:77,M:77,n:78,N:78,o:79,O:79,p:80,P:80,q:81,Q:81,r:82,R:82,s:83,S:83,t:84,T:84,u:85,U:85,v:86,V:86,w:87,W:87,x:88,X:88,y:89,Y:89,z:90,Z:90,OS:91,ContextMenu:93,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,NumLock:144,ScrollLock:145,VolumeMute:181,VolumeDown:182,VolumeUp:183,\";\":186,\":\":186,\"=\":187,\"+\":187,\",\":188,\"<\":188,\"-\":189,_:189,\".\":190,\">\":190,\"/\":191,\"?\":191,\"`\":192,\"~\":192,\"[\":219,\"{\":219,\"\\\\\":220,\"|\":220,\"]\":221,\"}\":221,\"'\":222,'\"':222,Meta:224,AltGraph:225,Attn:246,CrSel:247,ExSel:248,EraseEof:249,Play:250,ZoomOut:251};c.Spacebar=c[\" \"],c.Digit0=c[0],c.Digit1=c[1],c.Digit2=c[2],c.Digit3=c[3],c.Digit4=c[4],c.Digit5=c[5],c.Digit6=c[6],c.Digit7=c[7],c.Digit8=c[8],c.Digit9=c[9],c.Tilde=c[\"~\"],c.GraveAccent=c[\"`\"],c.ExclamationPoint=c[\"!\"],c.AtSign=c[\"@\"],c.PoundSign=c[\"#\"],c.PercentSign=c[\"%\"],c.Caret=c[\"^\"],c.Ampersand=c[\"&\"],c.PlusSign=c[\"+\"],c.MinusSign=c[\"-\"],c.EqualsSign=c[\"=\"],c.DivisionSign=c[\"/\"],c.MultiplicationSign=c[\"*\"],c.Comma=c[\",\"],c.Decimal=c[\".\"],c.Colon=c[\":\"],c.Semicolon=c[\";\"],c.Pipe=c[\"|\"],c.BackSlash=c[\"\\\\\"],c.QuestionMark=c[\"?\"],c.SingleQuote=c[\"'\"],c.DoubleQuote=c['\"'],c.LeftCurlyBrace=c[\"{\"],c.RightCurlyBrace=c[\"}\"],c.LeftParenthesis=c[\"(\"],c.RightParenthesis=c[\")\"],c.LeftAngleBracket=c[\"<\"],c.RightAngleBracket=c[\">\"],c.LeftSquareBracket=c[\"[\"],c.RightSquareBracket=c[\"]\"],e.exports=c},function(e,t,n){var r=n(160),o=n(64),a=n(93),i=o(function(e,t){return a(e)?r(e,t):[]});e.exports=i},function(e,t,n){\"use strict\";n.d(t,\"a\",function(){return i}),n.d(t,\"d\",function(){return l}),n.d(t,\"b\",function(){return c}),n.d(t,\"c\",function(){return u}),n.d(t,\"e\",function(){return s}),n.d(t,\"f\",function(){return f});var r=n(44),o=n.n(r),a=n(67),i=function(e,t){return e&&t},l=function(e,t){return e&&!0!==e&&\"\".concat(e,\" \").concat(t)},c=function(e,t){return e&&(!0===e?t:\"\".concat(e,\" \").concat(t))},u=function(e){return\"justified\"===e?\"justified\":l(e,\"aligned\")},s=function(e){return l(e,\"aligned\")},f=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"\";if(arguments.length>2&&void 0!==arguments[2]&&arguments[2]&&\"equal\"===e)return\"equal width\";var n=o()(e);return\"string\"!==n&&\"number\"!==n||!t?Object(a.a)(e):\"\".concat(Object(a.a)(e),\" \").concat(t)}},,function(e,t){e.exports=function(e){return null!=e&&\"object\"==typeof e}},function(e,t,n){var r=n(99);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},function(e,t,n){var r=n(161),o=\"object\"==typeof self&&self&&self.Object===Object&&self,a=r||o||Function(\"return this\")();e.exports=a},function(e,t,n){var r=n(0),o=n(253);e.exports=o(r)},function(e,t,n){var r=n(50),o=n(122);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},function(e,t,n){\"use strict\";var r=n(9),o=n.n(r),a=n(10),i=n.n(a),l=n(2),c=n.n(l),u=n(46),s=n(68),f=n.n(s),p=function(){function e(t){o()(this,e),this.handlers=new Set(t)}return i()(e,[{key:\"addHandlers\",value:function(t){var n=new Set(this.handlers);return t.forEach(function(e){n.delete(e),n.add(e)}),new e(n)}},{key:\"dispatchEvent\",value:function(e,t){t?this.handlers.forEach(function(t){t(e)}):f()(this.handlers).pop()(e)}},{key:\"hasHandlers\",value:function(){return this.handlers.size>0}},{key:\"removeHandlers\",value:function(t){var n=new Set(this.handlers);return t.forEach(function(e){n.delete(e)}),new e(n)}}]),e}(),d=function(){function e(t,n){o()(this,e),this.handlerSets=n,this.poolName=t}return i()(e,[{key:\"addHandlers\",value:function(t,n){var r=new Map(this.handlerSets);return r.has(t)?r.set(t,r.get(t).addHandlers(n)):r.set(t,new p(n)),new e(this.poolName,r)}},{key:\"dispatchEvent\",value:function(e,t){var n=this.handlerSets.get(e);n&&n.dispatchEvent(t,\"default\"===this.poolName)}},{key:\"hasHandlers\",value:function(e){var t=this.handlerSets.get(e);return!!t&&t.hasHandlers()}},{key:\"removeHandlers\",value:function(t,n){var r=new Map(this.handlerSets);if(!r.has(t))return new e(this.poolName,r);var o=r.get(t).removeHandlers(n);return o.hasHandlers()?r.set(t,o):r.delete(t),new e(this.poolName,r)}}]),e}();c()(d,\"createByType\",function(e,t,n){var r=new Map;return r.set(t,new p(n)),new d(e,r)});var h=function(){function e(t){o()(this,e),c()(this,\"handlers\",new Map),c()(this,\"pools\",new Map),c()(this,\"createEmitter\",function(e,t){return function(n){t.forEach(function(t){t.dispatchEvent(e,n)})}}),this.target=t}return i()(e,[{key:\"addHandlers\",value:function(e,t,n){this.removeTargetHandler(t),this.pools.has(e)?this.pools.set(e,this.pools.get(e).addHandlers(t,n)):this.pools.set(e,d.createByType(e,t,n)),this.addTargetHandler(t)}},{key:\"hasHandlers\",value:function(){return this.handlers.size>0}},{key:\"removeHandlers\",value:function(e,t,n){var r=this.pools.get(e);if(r){var o=r.removeHandlers(t,n);o.hasHandlers(t)?(this.removeTargetHandler(t),this.pools.set(e,o)):(this.removeTargetHandler(t),this.pools.delete(e)),this.pools.size>0&&this.addTargetHandler(t)}}},{key:\"addTargetHandler\",value:function(e){var t=this.createEmitter(e,this.pools);this.handlers.set(e,t),this.target.addEventListener(e,t)}},{key:\"removeTargetHandler\",value:function(e){this.handlers.has(e)&&(this.target.removeEventListener(e,this.handlers.get(e)),this.handlers.delete(e))}}]),e}(),v=n(21),m=n.n(v),y=function(e){return m()(e)?e:[e]},b=function(e){return\"document\"===e?document:\"window\"===e?window:e||document},g=new(function(){function e(){var t=this;o()(this,e),c()(this,\"targets\",new Map),c()(this,\"getTarget\",function(e){var n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=b(e);if(t.targets.has(r))return t.targets.get(r);if(!n)return null;var o=new h(r);return t.targets.set(r,o),o}),c()(this,\"removeTarget\",function(e){t.targets.delete(b(e))})}return i()(e,[{key:\"sub\",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(Object(u.a)()){var r=n.target,o=void 0===r?document:r,a=n.pool,i=void 0===a?\"default\":a;this.getTarget(o).addHandlers(i,e,y(t))}}},{key:\"unsub\",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(Object(u.a)()){var r=n.target,o=void 0===r?document:r,a=n.pool,i=void 0===a?\"default\":a,l=this.getTarget(o,!1);l&&(l.removeHandlers(i,e,y(t)),l.hasHandlers()||this.removeTarget(o))}}}]),e}());t.a=g},function(e,t,n){var r=n(56),o=n(263),a=n(264),i=\"[object Null]\",l=\"[object Undefined]\",c=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?l:i:c&&c in Object(e)?o(e):a(e)}},function(e,t,n){\"use strict\";function r(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}n.d(t,\"a\",function(){return r})},function(e,t,n){\"use strict\";var r=n(0),o=n.n(r),a=n(107),i=n(6),l=n.n(i),c=n(112),u=/^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|default|defer|dir|disabled|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|itemProp|itemScope|itemType|itemID|itemRef|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class)|(on[A-Z].*)|((data|aria|x)-.*))$/i,s=Object(c.a)(u.test.bind(u));var f,p=\"__EMOTION_THEMING__\",d=((f={})[p]=l.a.object,f);var h=s,v=function(e){return\"theme\"!==e&&\"innerRef\"!==e},m=function(){return!0},y=function(e,t){for(var n=2,r=arguments.length;n-1&&e%1==0&&e1&&void 0!==arguments[1]?arguments[1]:{},n=t.htmlProps,r=void 0===n?c:n,a=t.includeAria,l=void 0===a||a,u={},s={};return i()(e,function(e,t){var n=l&&(/^aria-.*$/.test(t)||\"role\"===t);(o()(r,t)||n?u:s)[t]=e}),[u,s]}},function(e,t){e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n-1}},function(e,t){e.exports=function(e,t){return e.has(t)}},function(e,t){e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},function(e,t,n){var r=n(33),o=n(29);e.exports=function(e){return o(e)&&r(e)}},function(e,t){e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach(function(e){n[++t]=e}),n}},function(e,t,n){var r=n(302),o=n(29),a=Object.prototype,i=a.hasOwnProperty,l=a.propertyIsEnumerable,c=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,\"callee\")&&!l.call(e,\"callee\")};e.exports=c},function(e,t,n){(function(e){var r=n(31),o=n(303),a=t&&!t.nodeType&&t,i=a&&\"object\"==typeof e&&e&&!e.nodeType&&e,l=i&&i.exports===a?r.Buffer:void 0,c=(l?l.isBuffer:void 0)||o;e.exports=c}).call(this,n(127)(e))},function(e,t,n){(function(e){var r=n(161),o=t&&!t.nodeType&&t,a=o&&\"object\"==typeof e&&e&&!e.nodeType&&e,i=a&&a.exports===o&&r.process,l=function(){try{var e=a&&a.require&&a.require(\"util\").types;return e||i&&i.binding&&i.binding(\"util\")}catch(t){}}();e.exports=l}).call(this,n(127)(e))},function(e,t,n){var r=n(72),o=n(305),a=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t=[];for(var n in Object(e))a.call(e,n)&&\"constructor\"!=n&&t.push(n);return t}},function(e,t,n){var r=n(73),o=n(59);e.exports=function(e,t){for(var n=0,a=(t=r(t,e)).length;null!=e&&n0&&a(s)?n>1?e(s,n-1,a,i,l):r(l,s):i||(l[l.length]=s)}return l}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n1&&void 0!==arguments[1]?arguments[1]:\"\",n=e&&e.split(\"/\")||[],r=t&&t.split(\"/\")||[],i=e&&o(e),l=t&&o(t),c=i||l;if(e&&o(e)?r=n:n.length&&(r.pop(),r=r.concat(n)),!r.length)return\"/\";var u=void 0;if(r.length){var s=r[r.length-1];u=\".\"===s||\"..\"===s||\"\"===s}else u=!1;for(var f=0,p=r.length;p>=0;p--){var d=r[p];\".\"===d?a(r,p):\"..\"===d?(a(r,p),f++):f&&(a(r,p),f--)}if(!c)for(;f--;f)r.unshift(\"..\");!c||\"\"===r[0]||r[0]&&o(r[0])||r.unshift(\"\");var h=r.join(\"/\");return u&&\"/\"!==h.substr(-1)&&(h+=\"/\"),h};\"function\"===typeof Symbol&&Symbol.iterator;var l=!0,c=\"Invariant failed\";var u=function(e,t){if(!e)throw l?new Error(c):new Error(c+\": \"+(t||\"\"))};function s(e){return\"/\"===e.charAt(0)?e:\"/\"+e}function f(e,t){return function(e,t){return new RegExp(\"^\"+t+\"(\\\\/|\\\\?|#|$)\",\"i\").test(e)}(e,t)?e.substr(t.length):e}function p(e){return\"/\"===e.charAt(e.length-1)?e.slice(0,-1):e}function d(e){var t=e.pathname,n=e.search,r=e.hash,o=t||\"/\";return n&&\"?\"!==n&&(o+=\"?\"===n.charAt(0)?n:\"?\"+n),r&&\"#\"!==r&&(o+=\"#\"===r.charAt(0)?r:\"#\"+r),o}function h(e,t,n,o){var a;\"string\"===typeof e?(a=function(e){var t=e||\"/\",n=\"\",r=\"\",o=t.indexOf(\"#\");-1!==o&&(r=t.substr(o),t=t.substr(0,o));var a=t.indexOf(\"?\");return-1!==a&&(n=t.substr(a),t=t.substr(0,a)),{pathname:t,search:\"?\"===n?\"\":n,hash:\"#\"===r?\"\":r}}(e)).state=t:(void 0===(a=r({},e)).pathname&&(a.pathname=\"\"),a.search?\"?\"!==a.search.charAt(0)&&(a.search=\"?\"+a.search):a.search=\"\",a.hash?\"#\"!==a.hash.charAt(0)&&(a.hash=\"#\"+a.hash):a.hash=\"\",void 0!==t&&void 0===a.state&&(a.state=t));try{a.pathname=decodeURI(a.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname \"'+a.pathname+'\" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(a.key=n),o?a.pathname?\"/\"!==a.pathname.charAt(0)&&(a.pathname=i(a.pathname,o.pathname)):a.pathname=o.pathname:a.pathname||(a.pathname=\"/\"),a}function v(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,o){if(null!=e){var a=\"function\"===typeof e?e(t,n):e;\"string\"===typeof a?\"function\"===typeof r?r(a,o):o(!0):o(!1!==a)}else o(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter(function(e){return e!==r})}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r-1&&e%1==0&&e<=n}},function(e,t,n){var r=n(86),o=n(293),a=n(294),i=n(295),l=n(296),c=n(297);function u(e){var t=this.__data__=new r(e);this.size=t.size}u.prototype.clear=o,u.prototype.delete=a,u.prototype.get=i,u.prototype.has=l,u.prototype.set=c,e.exports=u},function(e,t,n){var r=n(298),o=n(29);e.exports=function e(t,n,a,i,l){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!==t&&n!==n:r(t,n,a,i,e,l))}},function(e,t){e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++no?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var a=Array(o);++r3&&void 0!==arguments[3]&&arguments[3],a=t[e];if(void 0!==a)return a;if(o){var i=t[(r=e,\"default\".concat(r[0].toUpperCase()+r.slice(1)))];if(void 0!==i)return i;if(n){var l=n[e];if(void 0!==l)return l}}return\"checked\"!==e&&(\"value\"===e?t.multiple?[]:\"\":void 0)},C=function(e){function t(){var e,n;i()(this,t);for(var r=arguments.length,a=new Array(r),l=0;l0&&n.setState(a)});var c=n.constructor.autoControlledProps,u=w()(m()(m()(n)),\"getInitialAutoControlledState\",n.props)||{},f=c.reduce(function(e,t){return e[t]=j(t,n.props,u,!0),e},{});return n.state=o()({},u,f),n}return h()(t,e),c()(t,[{key:\"componentWillReceiveProps\",value:function(e){var t=this,n=this.constructor.autoControlledProps.reduce(function(n,r){var o=x()(e[r]),a=!x()(t.props[r])&&o;return o?a&&(n[r]=j(r,e)):n[r]=e[r],n},{});Object.keys(n).length>0&&this.setState(n)}}]),t}(k.Component)},function(e,t,n){e.exports=n(69)},function(e,t,n){var r=n(191);e.exports=function(e){return e&&e.length?r(e):[]}},function(e,t,n){var r=n(323),o=n(186),a=n(187);e.exports=function(e,t,n){return t=o(t),void 0===n?(n=t,t=0):n=o(n),e=a(e),r(e,t,n)}},function(e,t,n){var r=n(98),o=n(47),a=n(33),i=n(78),l=n(349),c=\"[object Map]\",u=\"[object Set]\";e.exports=function(e){if(null==e)return 0;if(a(e))return i(e)?l(e):e.length;var t=o(e);return t==c||t==u?e.size:r(e).length}},function(e,t,n){var r=n(101),o=n(64),a=n(191),i=n(93),l=o(function(e){return a(r(e,1,i,!0))});e.exports=l},,,,,,,,,,function(e,t,n){var r=n(84),o=n(90),a=n(120),i=n(57),l=n(63),c=n(91),u=200;e.exports=function(e,t,n,s){var f=-1,p=o,d=!0,h=e.length,v=[],m=t.length;if(!h)return v;n&&(t=i(t,l(n))),s?(p=a,d=!1):t.length>=u&&(p=c,d=!1,t=new r(t));e:for(;++f0){if(++t>=n)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}},function(e,t,n){var r=n(84),o=n(169),a=n(91),i=1,l=2;e.exports=function(e,t,n,c,u,s){var f=n&i,p=e.length,d=t.length;if(p!=d&&!(f&&d>p))return!1;var h=s.get(e);if(h&&s.get(t))return h==t;var v=-1,m=!0,y=n&l?new r:void 0;for(s.set(e,t),s.set(t,e);++v3&&void 0!==arguments[3]?arguments[3]:{};if(\"function\"!==typeof e&&\"string\"!==typeof e)throw new Error(\"createShorthand() Component must be a string or function.\");if(x()(n)||b()(n))return null;var a=m()(n),l=h()(n),u=p()(n),f=Object(k.isValidElement)(n),d=s()(n),v=a||l||c()(n);if(!u&&!f&&!d&&!v)return null;var y=r.defaultProps,g=void 0===y?{}:y,O=f&&n.props||d&&n||v&&t(n),C=r.overrideProps,E=void 0===C?{}:C;E=p()(E)?E(o()({},g,O)):E;var T=o()({},g,O,E);if(g.className||E.className||O.className){var _=w()(g.className,E.className,O.className);T.className=i()(_.split(\" \")).join(\" \")}if((g.style||E.style||O.style)&&(T.style=o()({},g.style,O.style,E.style)),x()(T.key)){var S=T.childKey,P=r.autoGenerateKey,N=void 0===P||P;x()(S)?N&&(a||l)&&(T.key=n):(T.key=\"function\"===typeof S?S(T):S,delete T.childKey)}return f?Object(k.cloneElement)(n,T):v||d?j.a.createElement(e,T):u?n(e,T,T.children):void 0}function E(e,t){if(\"function\"!==typeof e&&\"string\"!==typeof e)throw new Error(\"createShorthandFactory() Component must be a string or function.\");return function(n,r){return C(e,t,n,r)}}C.handledProps=[];E(\"div\",function(e){return{children:e}}),E(\"iframe\",function(e){return{src:e}}),E(\"img\",function(e){return{src:e}});var T=E(\"input\",function(e){return{type:e}}),_=E(\"label\",function(e){return{children:e}}),S=E(\"p\",function(e){return{children:e}})},function(e,t,n){var r=n(84),o=n(90),a=n(120),i=n(91),l=n(337),c=n(94),u=200;e.exports=function(e,t,n){var s=-1,f=o,p=e.length,d=!0,h=[],v=h;if(n)d=!1,f=a;else if(p>=u){var m=t?null:l(e);if(m)return c(m);d=!1,f=i,v=new r}else v=t?[]:h;e:for(;++s1&&h.reverse(),E&&j=o?e:r(e,t,n)}},function(e,t,n){e.exports=n(324)},function(e,t,n){var r=n(35),o=n(29),a=\"[object Boolean]\";e.exports=function(e){return!0===e||!1===e||o(e)&&r(e)==a}},function(e,t,n){e.exports=function(){\"use strict\";return function(e){function t(t){if(t)try{e(t+\"}\")}catch(n){}}return function(n,r,o,a,i,l,c,u,s,f){switch(n){case 1:if(0===s&&64===r.charCodeAt(0))return e(r+\";\"),\"\";break;case 2:if(0===u)return r+\"/*|*/\";break;case 3:switch(u){case 102:case 112:return e(o[0]+r),\"\";default:return r+(0===f?\"/*|*/\":\"\")}case-2:r.split(\"/*|*/}\").forEach(t)}}}}()},function(e,t){e.exports=function(e,t,n,r){var o=n?n.call(r,e,t):void 0;if(void 0!==o)return!!o;if(e===t)return!0;if(\"object\"!==typeof e||!e||\"object\"!==typeof t||!t)return!1;var a=Object.keys(e),i=Object.keys(t);if(a.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),c=0;c=4;)t=1540483477*(65535&(t=255&e.charCodeAt(o)|(255&e.charCodeAt(++o))<<8|(255&e.charCodeAt(++o))<<16|(255&e.charCodeAt(++o))<<24))+((1540483477*(t>>>16)&65535)<<16),r=1540483477*(65535&r)+((1540483477*(r>>>16)&65535)<<16)^(t=1540483477*(65535&(t^=t>>>24))+((1540483477*(t>>>16)&65535)<<16)),n-=4,++o;switch(n){case 3:r^=(255&e.charCodeAt(o+2))<<16;case 2:r^=(255&e.charCodeAt(o+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(o)))+((1540483477*(r>>>16)&65535)<<16)}return r=1540483477*(65535&(r^=r>>>13))+((1540483477*(r>>>16)&65535)<<16),((r^=r>>>15)>>>0).toString(36)};var i=function(e){function t(e,t,r){var o=t.trim().split(h);t=o;var a=o.length,i=e.length;switch(i){case 0:case 1:var l=0;for(e=0===i?\"\":e[0]+\" \";lr&&(r=(t=t.trim()).charCodeAt(0)),r){case 38:return t.replace(v,\"$1\"+e.trim());case 58:return e.trim()+t.replace(v,\"$1\"+e.trim());default:if(0<1*n&&0c.charCodeAt(8))break;case 115:i=i.replace(c,\"-webkit-\"+c)+\";\"+i;break;case 207:case 102:i=i.replace(c,\"-webkit-\"+(102l.charCodeAt(0)&&(l=l.trim()),l=[l],0d)&&(L=(B=B.replace(\" \",\":\")).length),01?o-1:0),l=1;lA.length&&A.push(e)}function M(e,t,n){return null==e?0:function e(t,n,r,o){var l=typeof t;\"undefined\"!==l&&\"boolean\"!==l||(t=null);var c=!1;if(null===t)c=!0;else switch(l){case\"string\":case\"number\":c=!0;break;case\"object\":switch(t.$$typeof){case a:case i:c=!0}}if(c)return r(o,t,\"\"===n?\".\"+D(t,0):n),1;if(c=0,n=\"\"===n?\".\":n+\":\",Array.isArray(t))for(var u=0;uthis.eventPool.length&&this.eventPool.push(e)}function fe(e){e.eventPool=[],e.getPooled=ue,e.release=se}o(ce.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():\"unknown\"!==typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=ie)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():\"unknown\"!==typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=ie)},persist:function(){this.isPersistent=ie},isPersistent:le,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=le,this._dispatchInstances=this._dispatchListeners=null}}),ce.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},ce.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var a=new t;return o(a,n.prototype),n.prototype=a,n.prototype.constructor=n,n.Interface=o({},r.Interface,e),n.extend=r.extend,fe(n),n},fe(ce);var pe=ce.extend({data:null}),de=ce.extend({data:null}),he=[9,13,27,32],ve=K&&\"CompositionEvent\"in window,me=null;K&&\"documentMode\"in document&&(me=document.documentMode);var ye=K&&\"TextEvent\"in window&&!me,be=K&&(!ve||me&&8=me),ge=String.fromCharCode(32),xe={beforeInput:{phasedRegistrationNames:{bubbled:\"onBeforeInput\",captured:\"onBeforeInputCapture\"},dependencies:[\"compositionend\",\"keypress\",\"textInput\",\"paste\"]},compositionEnd:{phasedRegistrationNames:{bubbled:\"onCompositionEnd\",captured:\"onCompositionEndCapture\"},dependencies:\"blur compositionend keydown keypress keyup mousedown\".split(\" \")},compositionStart:{phasedRegistrationNames:{bubbled:\"onCompositionStart\",captured:\"onCompositionStartCapture\"},dependencies:\"blur compositionstart keydown keypress keyup mousedown\".split(\" \")},compositionUpdate:{phasedRegistrationNames:{bubbled:\"onCompositionUpdate\",captured:\"onCompositionUpdateCapture\"},dependencies:\"blur compositionupdate keydown keypress keyup mousedown\".split(\" \")}},Oe=!1;function we(e,t){switch(e){case\"keyup\":return-1!==he.indexOf(t.keyCode);case\"keydown\":return 229!==t.keyCode;case\"keypress\":case\"mousedown\":case\"blur\":return!0;default:return!1}}function ke(e){return\"object\"===typeof(e=e.detail)&&\"data\"in e?e.data:null}var je=!1;var Ce={eventTypes:xe,extractEvents:function(e,t,n,r){var o=void 0,a=void 0;if(ve)e:{switch(e){case\"compositionstart\":o=xe.compositionStart;break e;case\"compositionend\":o=xe.compositionEnd;break e;case\"compositionupdate\":o=xe.compositionUpdate;break e}o=void 0}else je?we(e,n)&&(o=xe.compositionEnd):\"keydown\"===e&&229===n.keyCode&&(o=xe.compositionStart);return o?(be&&\"ko\"!==n.locale&&(je||o!==xe.compositionStart?o===xe.compositionEnd&&je&&(a=ae()):(re=\"value\"in(ne=r)?ne.value:ne.textContent,je=!0)),o=pe.getPooled(o,t,n,r),a?o.data=a:null!==(a=ke(n))&&(o.data=a),V(o),a=o):a=null,(e=ye?function(e,t){switch(e){case\"compositionend\":return ke(t);case\"keypress\":return 32!==t.which?null:(Oe=!0,ge);case\"textInput\":return(e=t.data)===ge&&Oe?null:e;default:return null}}(e,n):function(e,t){if(je)return\"compositionend\"===e||!ve&&we(e,t)?(e=ae(),oe=re=ne=null,je=!1,e):null;switch(e){case\"paste\":return null;case\"keypress\":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1