Skip to content

Commit 42427a7

Browse files
committed
refactor: use GetKeystoneAPIByName instead of label-based lookup
- Replace GetKeystoneAPI with GetKeystoneAPIByName in nova_controller.go - use instance.Spec.KeystoneInstance directly instead of empty label map - Remove TODO comment about changing the API usage. - Update keystone-operator/api dependency to support new functionality This provides a more direct and efficient way to get the KeystoneAPI resource by name rather then searching by labels. Commit message assisted by: claude-4-sonnet
1 parent a68262c commit 42427a7

File tree

7 files changed

+66
-23
lines changed

7 files changed

+66
-23
lines changed

controllers/nova_controller.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,10 +1649,7 @@ func (r *NovaReconciler) getKeystoneAuthURL(
16491649
h *helper.Helper,
16501650
instance *novav1.Nova,
16511651
) (string, string, error) {
1652-
// TODO(gibi): change lib-common to take the name of the KeystoneAPI as
1653-
// parameter instead of labels. Then use instance.Spec.KeystoneInstance as
1654-
// the name.
1655-
keystoneAPI, err := keystonev1.GetKeystoneAPI(ctx, h, instance.Namespace, map[string]string{})
1652+
keystoneAPI, err := keystonev1.GetKeystoneAPIByName(ctx, h, instance.Spec.KeystoneInstance, instance.Namespace)
16561653
if err != nil {
16571654
return "", "", err
16581655
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ require (
8787

8888
replace github.com/openstack-k8s-operators/nova-operator/api => ./api
8989

90+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250818061624-cbdfd1f68df8 //allow-merging
91+
9092
// mschuppert: map to latest commit from release-4.16 tag
9193
// must consistent within modules and service operators
9294
replace github.com/openshift/api => github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 //allow-merging

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 h1:J1wuGhVxpsHykZBa6
8080
github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4=
8181
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250813063935-fdc20530dcf1 h1:77TRnwfSxNI5cn/RxMS9I+kqefMm7XRQsNknIhEE4tg=
8282
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250813063935-fdc20530dcf1/go.mod h1:Dv8qpmBIQy3Jv/EyQnOyc0w61X8vyfxpjcIQONP5CwY=
83-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250811083324-e790d63f389f h1:Ivo4YKaH26B1lQlwKcolELCRGtEbmvbfJShBTVrCdQI=
84-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250811083324-e790d63f389f/go.mod h1:H5iZOohoVOmZvIZPR5ep6z+jmfrpz25axOM6IXlXzNU=
83+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250818061624-cbdfd1f68df8 h1:1r3wSalcW2TY8K3X6j5cL0M9MgJDCI1nZ7tHne/Izlg=
84+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250818061624-cbdfd1f68df8/go.mod h1:H5iZOohoVOmZvIZPR5ep6z+jmfrpz25axOM6IXlXzNU=
8585
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250730071847-837b07f8d72f h1:DW8aNjEtDFrWiZ6vWuOXwdRB4eBD0n+bA9foQkOEx6U=
8686
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250730071847-837b07f8d72f/go.mod h1:P+7F1wiwZUxOy4myYXFyc/uBtGATDFpk3yAllXe1Vzk=
8787
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250730071847-837b07f8d72f h1:nGYLHcpM7EjiSzN4bmiLZbxty9u0k0Qzvkqn+1s1TF0=

test/functional/base_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,17 +1042,20 @@ func CreateNovaWithNCellsAndEnsureReady(cellNumber int, novaNames *NovaNames) {
10421042
cellTemplates[cellName] = template
10431043
}
10441044

1045+
// Create KeystoneAPI first to get the correct name
1046+
// DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1047+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1048+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
1049+
10451050
// Create Nova spec
10461051
spec := GetDefaultNovaSpec()
10471052
spec["cellTemplates"] = cellTemplates
10481053
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
10491054
spec["apiMessageBusInstance"] = novaNames.Cells["cell0"].TransportURLName.Name
1055+
spec["keystoneInstance"] = keystoneAPIName.Name
10501056

10511057
// Deploy Nova and simulate its dependencies
10521058
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
1053-
// DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1054-
novaNames.KeystoneAPIName = keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1055-
DeferCleanup(keystone.DeleteKeystoneAPI, novaNames.KeystoneAPIName)
10561059

10571060
memcachedSpec := infra.GetDefaultMemcachedSpec()
10581061
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))

test/functional/nova_controller_test.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -759,13 +759,17 @@ var _ = Describe("Nova controller", func() {
759759

760760
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
761761
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
762-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
762+
763+
// Create KeystoneAPI first to get correct name
764+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
765+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
763766

764767
spec := GetDefaultNovaSpec()
765768
cell0template := GetDefaultNovaCellTemplate()
766769
cell0template["cellDatabaseInstance"] = cell0.MariaDBDatabaseName.Name
767770
spec["cellTemplates"] = map[string]interface{}{"cell0": cell0template}
768771
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
772+
spec["keystoneInstance"] = keystoneAPIName.Name
769773

770774
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
771775
})
@@ -1100,7 +1104,9 @@ var _ = Describe("Nova controller", func() {
11001104
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
11011105
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
11021106

1103-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1107+
// Create KeystoneAPI first to get the correct name
1108+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1109+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
11041110

11051111
spec := GetDefaultNovaSpec()
11061112
cell0template := GetDefaultNovaCellTemplate()
@@ -1109,6 +1115,7 @@ var _ = Describe("Nova controller", func() {
11091115
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
11101116
spec["apiDatabaseAccount"] = novaNames.APIMariaDBDatabaseAccount.Name
11111117
spec["topologyRef"] = map[string]interface{}{"name": "foo"}
1118+
spec["keystoneInstance"] = keystoneAPIName.Name
11121119

11131120
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
11141121

@@ -1176,14 +1183,17 @@ var _ = Describe("Nova controller", func() {
11761183
// Create a global Test Topology
11771184
_, topologyRef = infra.CreateTopology(novaNames.NovaTopologies[0], topologySpec)
11781185

1179-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1186+
// Create keystoneAPI first to get correct name
1187+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1188+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
11801189

11811190
spec := GetDefaultNovaSpec()
11821191
cell0template := GetDefaultNovaCellTemplate()
11831192
cell0template["cellDatabaseInstance"] = cell0.MariaDBDatabaseName.Name
11841193
spec["cellTemplates"] = map[string]interface{}{"cell0": cell0template}
11851194
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
11861195
spec["apiDatabaseAccount"] = novaNames.APIMariaDBDatabaseAccount.Name
1196+
spec["keystoneInstance"] = keystoneAPIName.Name
11871197

11881198
// We reference the global topology and is inherited by the sub components
11891199
spec["topologyRef"] = map[string]interface{}{"name": topologyRef.Name}
@@ -1284,6 +1294,9 @@ var _ = Describe("Nova controller", func() {
12841294
DeferCleanup(mariadb.DeleteDBService, mariadb.CreateDBService(novaNames.APIMariaDBDatabaseName.Namespace, novaNames.APIMariaDBDatabaseName.Name, serviceSpec))
12851295
DeferCleanup(mariadb.DeleteDBService, mariadb.CreateDBService(cell0.MariaDBDatabaseName.Namespace, cell0.MariaDBDatabaseName.Name, serviceSpec))
12861296
DeferCleanup(mariadb.DeleteDBService, mariadb.CreateDBService(cell1.MariaDBDatabaseName.Namespace, cell1.MariaDBDatabaseName.Name, serviceSpec))
1297+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1298+
keystoneAPI := keystone.GetKeystoneAPI(keystoneAPIName)
1299+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
12871300
// cell0
12881301
spec := GetDefaultNovaSpec()
12891302
cell0Template := GetDefaultNovaCellTemplate()
@@ -1313,13 +1326,11 @@ var _ = Describe("Nova controller", func() {
13131326
// We reference the global topology and is inherited by the sub components
13141327
// except cell1 that has an override
13151328
spec["topologyRef"] = map[string]interface{}{"name": topologyRefTopLevel.Name}
1329+
spec["keystoneInstance"] = keystoneAPIName.Name
13161330
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
13171331
memcachedSpec := infra.GetDefaultMemcachedSpec()
13181332
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
13191333
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
1320-
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1321-
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
1322-
keystoneAPI := keystone.GetKeystoneAPI(keystoneAPIName)
13231334
Eventually(func(g Gomega) {
13241335
g.Expect(k8sClient.Status().Update(ctx, keystoneAPI.DeepCopy())).Should(Succeed())
13251336
}, timeout, interval).Should(Succeed())
@@ -1449,11 +1460,16 @@ var _ = Describe("Nova controller", func() {
14491460
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
14501461
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
14511462

1463+
// Create keystoneAPI first to get the correct name
1464+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1465+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
1466+
14521467
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
14531468

14541469
spec := GetDefaultNovaSpec()
14551470
cell0 := GetDefaultNovaCellTemplate()
14561471
spec["cellTemplates"] = map[string]interface{}{"cell0": cell0}
1472+
spec["keystoneInstance"] = keystoneAPIName.Name
14571473
// This nova is created without any container image is specified in
14581474
// the request
14591475
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
@@ -1533,14 +1549,17 @@ var _ = Describe("Nova controller", func() {
15331549
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
15341550
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
15351551

1536-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1552+
// Create keystoneAPI first to get the correct name
1553+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1554+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
15371555

15381556
spec := GetDefaultNovaSpec()
15391557
cell0template := GetDefaultNovaCellTemplate()
15401558
cell0template["cellDatabaseInstance"] = cell0.MariaDBDatabaseName.Name
15411559
spec["cellTemplates"] = map[string]interface{}{"cell0": cell0template}
15421560
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
15431561
spec["apiDatabaseAccount"] = accountName.Name
1562+
spec["keystoneInstance"] = keystoneAPIName.Name
15441563

15451564
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
15461565

@@ -1637,14 +1656,17 @@ var _ = Describe("Nova controller", func() {
16371656
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
16381657
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
16391658

1640-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
1659+
// Create keystoneAPI first to get the correct name
1660+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1661+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
16411662

16421663
spec := GetDefaultNovaSpec()
16431664
cell0template := GetDefaultNovaCellTemplate()
16441665
cell0template["cellDatabaseInstance"] = cell0.MariaDBDatabaseName.Name
16451666
cell0template["cellDatabaseAccount"] = accountName.Name
16461667
spec["cellTemplates"] = map[string]interface{}{"cell0": cell0template}
16471668
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
1669+
spec["keystoneInstance"] = keystoneAPIName.Name
16481670

16491671
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
16501672

test/functional/nova_multicell_test.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,20 @@ var _ = Describe("Nova multi cell", func() {
9898
"database": "NovaCell2DatabasePassword",
9999
}
100100

101+
// Create KeystoneAPI first to get the correct name
102+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
103+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
104+
101105
spec["cellTemplates"] = map[string]interface{}{
102106
"cell0": cell0Template,
103107
"cell1": cell1Template,
104108
"cell2": cell2Template,
105109
}
106110
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
107111
spec["apiMessageBusInstance"] = cell0.TransportURLName.Name
112+
spec["keystoneInstance"] = keystoneAPIName.Name
108113

109114
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
110-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
111115
memcachedSpecCell1 := infra.GetDefaultMemcachedSpec()
112116
memcachedNamespace := types.NamespacedName{
113117
Name: cell1Memcached,
@@ -678,19 +682,23 @@ var _ = Describe("Nova multi cell", func() {
678682
cell1Template["cellMessageBusInstance"] = cell0.TransportURLName.Name
679683
cell1Template["hasAPIAccess"] = true
680684

685+
// Create KeystoneAPI first to get the correct name
686+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
687+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
688+
681689
spec["cellTemplates"] = map[string]interface{}{
682690
"cell0": cell0Template,
683691
"cell1": cell1Template,
684692
}
685693
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
686694
spec["apiMessageBusInstance"] = cell0.TransportURLName.Name
695+
spec["keystoneInstance"] = keystoneAPIName.Name
687696

688697
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
689698
memcachedSpec := infra.GetDefaultMemcachedSpec()
690699

691700
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
692701
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
693-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace))
694702
keystone.SimulateKeystoneServiceReady(novaNames.KeystoneServiceName)
695703

696704
})
@@ -792,19 +800,23 @@ var _ = Describe("Nova multi cell", func() {
792800
cell1Template["cellDatabaseAccount"] = cell1.MariaDBAccountName.Name
793801
cell1Template["cellMessageBusInstance"] = cell1.TransportURLName.Name
794802

803+
// Create KeystoneAPI first to get the correct name
804+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.Namespace)
805+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
806+
795807
spec["cellTemplates"] = map[string]interface{}{
796808
"cell0": cell0Template,
797809
"cell1": cell1Template,
798810
}
799811
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
800812
spec["apiMessageBusInstance"] = cell0.TransportURLName.Name
813+
spec["keystoneInstance"] = keystoneAPIName.Name
801814

802815
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
803816
memcachedSpec := infra.GetDefaultMemcachedSpec()
804817

805818
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
806819
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
807-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(novaNames.Namespace))
808820
keystone.SimulateKeystoneServiceReady(novaNames.KeystoneServiceName)
809821
})
810822

@@ -858,19 +870,22 @@ var _ = Describe("Nova multi cell", func() {
858870
"cell0": cell0Template,
859871
"cell1": cell1Template,
860872
}
873+
// Create KeystoneAPI first to get the correct name
874+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
875+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
876+
861877
spec["metadataServiceTemplate"] = map[string]interface{}{
862878
"enabled": false,
863879
}
864880
spec["apiDatabaseInstance"] = novaNames.APIMariaDBDatabaseName.Name
865881
spec["apiMessageBusInstance"] = cell0.TransportURLName.Name
882+
spec["keystoneInstance"] = keystoneAPIName.Name
866883

867884
DeferCleanup(th.DeleteInstance, CreateNova(novaNames.NovaName, spec))
868885
memcachedSpec := infra.GetDefaultMemcachedSpec()
869886

870887
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(novaNames.NovaName.Namespace, MemcachedInstance, memcachedSpec))
871888
infra.SimulateMemcachedReady(novaNames.MemcachedNamespace)
872-
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
873-
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
874889
keystoneAPI := keystone.GetKeystoneAPI(keystoneAPIName)
875890
Eventually(func(g Gomega) {
876891
g.Expect(k8sClient.Status().Update(ctx, keystoneAPI.DeepCopy())).Should(Succeed())

test/functional/nova_reconfiguration_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,11 @@ var _ = Describe("Nova reconfiguration", func() {
10601060
It("updates the KeystoneAuthURL of the sub components if keystone internal endpoint changes", func() {
10611061
newInternalEndpoint := "https://keystone-internal"
10621062

1063-
keystone.UpdateKeystoneAPIEndpoint(novaNames.KeystoneAPIName, "internal", newInternalEndpoint)
1063+
// Create keystoneAPI first to get the correct name
1064+
keystoneAPIName := keystone.CreateKeystoneAPI(novaNames.NovaName.Namespace)
1065+
1066+
keystone.UpdateKeystoneAPIEndpoint(keystoneAPIName, "internal", newInternalEndpoint)
1067+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
10641068
logger.Info("Reconfigured")
10651069

10661070
SimulateReadyOfNovaTopServices()

0 commit comments

Comments
 (0)