Skip to content

Commit 746fcba

Browse files
authored
Merge pull request ceph#62100 from rhcs-dashboard/bucket-lifecycle-fixes
mgr/dashboard: bucket lifecycle fixes after using xmltodict package Reviewed-by: Afreen Misbah <[email protected]> Reviewed-by: Aashish Sharma <[email protected]>
2 parents 316f14b + 8f7f923 commit 746fcba

File tree

5 files changed

+117
-42
lines changed

5 files changed

+117
-42
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export interface Lifecycle {
2+
LifecycleConfiguration: LifecycleConfiguration;
3+
}
4+
5+
export interface LifecycleConfiguration {
6+
Rule: Rule[];
7+
}
8+
9+
export interface Rule {
10+
ID: string;
11+
Status: string;
12+
Transition: Transition;
13+
Prefix?: string;
14+
Filter?: Filter;
15+
}
16+
17+
export interface Filter {
18+
And?: And;
19+
Prefix?: string;
20+
Tag?: Tag | Tag[];
21+
}
22+
23+
export interface And {
24+
Prefix: string;
25+
Tag: Tag | Tag[];
26+
}
27+
28+
export interface Tag {
29+
Key: string;
30+
Value: string;
31+
}
32+
33+
export interface Transition {
34+
Days: string;
35+
StorageClass: string;
36+
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-lifecycle-list/rgw-bucket-lifecycle-list.component.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Observable, of } from 'rxjs';
1717
import { catchError, map, tap } from 'rxjs/operators';
1818
import { NotificationService } from '~/app/shared/services/notification.service';
1919
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
20+
import { Lifecycle, Rule } from '../models/rgw-bucket-lifecycle';
2021

2122
@Component({
2223
selector: 'cd-rgw-bucket-lifecycle-list',
@@ -96,7 +97,7 @@ export class RgwBucketLifecycleListComponent implements OnInit {
9697
const allLifecycleRules$ = this.rgwBucketService
9798
.getLifecycle(this.bucket.bucket, this.bucket.owner)
9899
.pipe(
99-
tap((lifecycle) => {
100+
tap((lifecycle: Lifecycle) => {
100101
this.lifecycleRuleList = lifecycle;
101102
}),
102103
catchError(() => {
@@ -108,7 +109,7 @@ export class RgwBucketLifecycleListComponent implements OnInit {
108109
this.filteredLifecycleRules$ = allLifecycleRules$.pipe(
109110
map(
110111
(lifecycle: any) =>
111-
lifecycle?.LifecycleConfiguration?.Rules?.filter((rule: object) =>
112+
lifecycle?.LifecycleConfiguration?.Rule?.filter((rule: Rule) =>
112113
rule.hasOwnProperty('Transition')
113114
) || []
114115
)
@@ -130,10 +131,10 @@ export class RgwBucketLifecycleListComponent implements OnInit {
130131

131132
deleteAction() {
132133
const ruleNames = this.selection.selected.map((rule) => rule.ID);
133-
const filteredRules = this.lifecycleRuleList.LifecycleConfiguration.Rules.filter(
134-
(rule: any) => !ruleNames.includes(rule.ID)
134+
const filteredRules = this.lifecycleRuleList.LifecycleConfiguration.Rule.filter(
135+
(rule: Rule) => !ruleNames.includes(rule.ID)
135136
);
136-
const rules = filteredRules.length > 0 ? { Rules: filteredRules } : {};
137+
const rules = filteredRules.length > 0 ? { Rule: filteredRules } : {};
137138
this.modalRef = this.modalService.show(DeleteConfirmationModalComponent, {
138139
itemDescription: $localize`Rule`,
139140
itemNames: ruleNames,

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-tiering-form/rgw-bucket-tiering-form.component.html

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,25 +136,35 @@
136136
class="form-item form-item-append">
137137
<div cdsCol>
138138
<cds-text-label labelInputID="Key"
139+
[invalid]="!tieringForm.controls['tags'].controls[i].controls['Key'].valid && tieringForm.controls['tags'].controls[i].controls['Key'].dirty"
140+
[invalidText]="tagKeyError"
139141
i18n>Name of the object key
140142
<input cdsText
141143
type="text"
142144
placeholder="Enter name of the object key"
143-
id="Key"
144145
formControlName="Key"
145-
i18n-placeholder/>
146+
i18n-placeholder
147+
[invalid]="tieringForm.controls['tags'].controls[i].controls['Key'].invalid && tieringForm.controls['tags'].controls[i].controls['Key'].dirty"/>
146148
</cds-text-label>
149+
<ng-template #tagKeyError>
150+
<ng-container i18n>This field is required.</ng-container>
151+
</ng-template>
147152
</div>
148153
<div cdsCol>
149154
<cds-text-label labelInputID="Value"
155+
[invalid]="!tieringForm.controls['tags'].controls[i].controls['Value'].valid && tieringForm.controls['tags'].controls[i].controls['Value'].dirty"
156+
[invalidText]="tagValueError"
150157
i18n>Value of the tag
151158
<input cdsText
152159
type="text"
153160
placeholder="Enter value of the tag"
154-
id="Value"
155161
formControlName="Value"
156-
i18n-placeholder/>
162+
i18n-placeholder
163+
[invalid]="tieringForm.controls['tags'].controls[i].controls['Value'].invalid && tieringForm.controls['tags'].controls[i].controls['Value'].dirty"/>
157164
</cds-text-label>
165+
<ng-template #tagValueError>
166+
<ng-container i18n>This field is required.</ng-container>
167+
</ng-template>
158168
</div>
159169
<div cdsCol
160170
[columnNumbers]="{ lg: 2, md: 2 }"

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-tiering-form/rgw-bucket-tiering-form.component.ts

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { StorageClass, ZoneGroupDetails } from '../models/rgw-storage-class.mode
2020
import { CdForm } from '~/app/shared/forms/cd-form';
2121
import { Router } from '@angular/router';
2222
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
23+
import { Lifecycle, Rule, Tag } from '../models/rgw-bucket-lifecycle';
2324

2425
export interface Tags {
2526
tagKey: number;
@@ -35,12 +36,12 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
3536
tieringForm: CdFormGroup;
3637
tagsToRemove: Tags[] = [];
3738
storageClassList: StorageClass[] = null;
38-
configuredLifecycle: any;
39+
configuredLifecycle: Lifecycle;
3940
isStorageClassFetched = false;
4041

4142
constructor(
4243
@Inject('bucket') public bucket: Bucket,
43-
@Optional() @Inject('selectedLifecycle') public selectedLifecycle: any,
44+
@Optional() @Inject('selectedLifecycle') public selectedLifecycle: Rule,
4445
@Optional() @Inject('editing') public editing = false,
4546
public actionLabels: ActionLabelsI18n,
4647
private rgwBucketService: RgwBucketService,
@@ -56,11 +57,28 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
5657
ngOnInit() {
5758
this.rgwBucketService
5859
.getLifecycle(this.bucket.bucket, this.bucket.owner)
59-
.subscribe((lifecycle) => {
60-
this.configuredLifecycle = lifecycle || { LifecycleConfiguration: { Rules: [] } };
60+
.subscribe((lifecycle: Lifecycle) => {
61+
if (lifecycle === null) {
62+
lifecycle = { LifecycleConfiguration: { Rule: [] } };
63+
}
64+
lifecycle.LifecycleConfiguration.Rule = lifecycle.LifecycleConfiguration.Rule.map(
65+
(rule: Rule) => {
66+
if (rule?.['Filter']?.['Tag'] && !Array.isArray(rule?.['Filter']?.['Tag'])) {
67+
rule['Filter']['Tag'] = [rule['Filter']['Tag']];
68+
}
69+
if (
70+
rule?.['Filter']?.['And']?.['Tag'] &&
71+
!Array.isArray(rule?.['Filter']?.['And']?.['Tag'])
72+
) {
73+
rule['Filter']['And']['Tag'] = [rule['Filter']['And']['Tag']];
74+
}
75+
return rule;
76+
}
77+
);
78+
this.configuredLifecycle = lifecycle;
6179
if (this.editing) {
62-
const ruleToEdit = this.configuredLifecycle?.['LifecycleConfiguration']?.['Rules'].filter(
63-
(rule: any) => rule?.['ID'] === this.selectedLifecycle?.['ID']
80+
const ruleToEdit = this.configuredLifecycle?.['LifecycleConfiguration']?.['Rule'].filter(
81+
(rule: Rule) => rule?.['ID'] === this.selectedLifecycle?.['ID']
6482
)[0];
6583
this.tieringForm.patchValue({
6684
name: ruleToEdit?.['ID'],
@@ -89,35 +107,35 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
89107
this.loadStorageClass();
90108
}
91109

92-
checkIfRuleHasFilters(rule: any) {
110+
checkIfRuleHasFilters(rule: Rule) {
93111
if (
94112
this.isValidPrefix(rule?.['Prefix']) ||
95113
this.isValidPrefix(rule?.['Filter']?.['Prefix']) ||
96-
this.isValidArray(rule?.['Filter']?.['Tags']) ||
114+
this.isValidArray(rule?.['Filter']?.['Tag']) ||
97115
this.isValidPrefix(rule?.['Filter']?.['And']?.['Prefix']) ||
98-
this.isValidArray(rule?.['Filter']?.['And']?.['Tags'])
116+
this.isValidArray(rule?.['Filter']?.['And']?.['Tag'])
99117
) {
100118
return true;
101119
}
102120
return false;
103121
}
104122

105123
isValidPrefix(value: string) {
106-
return value !== undefined && value !== '';
124+
return !!value;
107125
}
108126

109-
isValidArray(value: object[]) {
127+
isValidArray(value: Tag | Tag[]) {
110128
return Array.isArray(value) && value.length > 0;
111129
}
112130

113-
setTags(rule: any) {
114-
if (rule?.['Filter']?.['Tags']?.length > 0) {
115-
rule?.['Filter']?.['Tags']?.forEach((tag: { Key: string; Value: string }) =>
131+
setTags(rule: Rule) {
132+
if (Array.isArray(rule?.Filter?.Tag) && rule?.Filter?.Tag?.length > 0) {
133+
rule?.['Filter']?.['Tag']?.forEach((tag: { Key: string; Value: string }) =>
116134
this.addTags(tag.Key, tag.Value)
117135
);
118136
}
119-
if (rule?.['Filter']?.['And']?.['Tags']?.length > 0) {
120-
rule?.['Filter']?.['And']?.['Tags']?.forEach((tag: { Key: string; Value: string }) =>
137+
if (Array.isArray(rule?.Filter?.And?.Tag) && rule?.Filter?.And?.Tag?.length > 0) {
138+
rule?.['Filter']?.['And']?.['Tag']?.forEach((tag: { Key: string; Value: string }) =>
121139
this.addTags(tag.Key, tag.Value)
122140
);
123141
}
@@ -130,17 +148,17 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
130148
addTags(key?: string, value?: string) {
131149
this.tags.push(
132150
new FormGroup({
133-
Key: new FormControl(key),
134-
Value: new FormControl(value)
151+
Key: new FormControl(key || '', Validators.required),
152+
Value: new FormControl(value || '', Validators.required)
135153
})
136154
);
137155
this.cd.detectChanges();
138156
}
139157

140158
duplicateConfigName(control: AbstractControl): ValidationErrors | null {
141-
if (this.configuredLifecycle?.LifecycleConfiguration?.Rules?.length > 0) {
142-
const ruleIds = this.configuredLifecycle.LifecycleConfiguration.Rules.map(
143-
(rule: any) => rule.ID
159+
if (this.configuredLifecycle?.LifecycleConfiguration?.Rule?.length > 0) {
160+
const ruleIds = this.configuredLifecycle.LifecycleConfiguration.Rule.map(
161+
(rule: Rule) => rule.ID
144162
);
145163
return ruleIds.includes(control.value) ? { duplicate: true } : null;
146164
}
@@ -181,15 +199,13 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
181199
return;
182200
}
183201

184-
let lifecycle: any = {
202+
let lifecycle: Rule = {
185203
ID: this.tieringForm.getRawValue().name,
186204
Status: formValue.status,
187-
Transition: [
188-
{
189-
Days: formValue.days,
190-
StorageClass: formValue.storageClass
191-
}
192-
]
205+
Transition: {
206+
Days: formValue.days,
207+
StorageClass: formValue.storageClass
208+
}
193209
};
194210
if (formValue.hasPrefix) {
195211
if (this.tags.length > 0) {
@@ -210,11 +226,11 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
210226
}
211227
} else {
212228
Object.assign(lifecycle, {
213-
Filter: {}
229+
Prefix: ''
214230
});
215231
}
216232
if (!this.editing) {
217-
this.configuredLifecycle.LifecycleConfiguration.Rules.push(lifecycle);
233+
this.configuredLifecycle.LifecycleConfiguration.Rule.push(lifecycle);
218234
this.rgwBucketService
219235
.setLifecycle(
220236
this.bucket.bucket,
@@ -237,8 +253,10 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
237253
}
238254
});
239255
} else {
240-
const rules = this.configuredLifecycle.LifecycleConfiguration.Rules;
241-
const index = rules.findIndex((rule: any) => rule?.['ID'] === this.selectedLifecycle?.['ID']);
256+
const rules = this.configuredLifecycle.LifecycleConfiguration.Rule;
257+
const index = rules.findIndex(
258+
(rule: Rule) => rule?.['ID'] === this.selectedLifecycle?.['ID']
259+
);
242260
rules.splice(index, 1, lifecycle);
243261
this.rgwBucketService
244262
.setLifecycle(
@@ -266,5 +284,6 @@ export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
266284

267285
goToCreateStorageClass() {
268286
this.router.navigate(['rgw/tiering/create']);
287+
this.closeModal();
269288
}
270289
}

src/pybind/mgr/dashboard/services/rgw_client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,16 @@ def remove_namespace(xml: str):
737737
try:
738738
result = request(
739739
raw_content=True, headers={'Accept': 'text/xml'}).decode() # type: ignore
740-
return xmltodict.parse(remove_namespace(result), process_namespaces=False)
740+
lifecycle = xmltodict.parse(remove_namespace(result), process_namespaces=False)
741+
if lifecycle is not None:
742+
lifecycle_config = lifecycle.get('LifecycleConfiguration', {})
743+
rule = lifecycle_config.get('Rule')
744+
745+
if isinstance(rule, dict):
746+
lifecycle_config['Rule'] = [rule]
747+
748+
lifecycle['LifecycleConfiguration'] = lifecycle_config
749+
return lifecycle
741750
except RequestException as e:
742751
if e.content:
743752
root = ET.fromstring(e.content)

0 commit comments

Comments
 (0)