Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the medusa standalone pod #1304

Merged
merged 2 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG/CHANGELOG-1.16.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ When cutting a new release, update the `unreleased` heading to the tag being gen

## unreleased

* [BUGFIX] [#1272](https://github.com/k8ssandra/k8ssandra-operator/issues/1272) Prevent cass-operator from creating users when an external DC is referenced to allow migration through expansion
* [BUGFIX] [#1272](https://github.com/k8ssandra/k8ssandra-operator/issues/1272) Prevent cass-operator from creating users when an external DC is referenced to allow migration through expansion
* [ENHANCEMENT] [#1066](https://github.com/k8ssandra/k8ssandra-operator/issues/1066) Remove the medusa standalone pod as it is not needed anymore
7 changes: 0 additions & 7 deletions controllers/k8ssandra/k8ssandracluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/k8ssandra/k8ssandra-operator/pkg/encryption"
"github.com/k8ssandra/k8ssandra-operator/pkg/images"
"github.com/k8ssandra/k8ssandra-operator/pkg/labels"
medusa "github.com/k8ssandra/k8ssandra-operator/pkg/medusa"
"github.com/k8ssandra/k8ssandra-operator/pkg/utils"

promapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
Expand Down Expand Up @@ -1582,8 +1581,6 @@ func applyClusterWithEncryptionOptions(t *testing.T, ctx context.Context, f *fra

verifySystemReplicationAnnotationSet(ctx, t, f, kc)

medusaKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), "dc1")}, K8sContext: f.DataPlaneContexts[0]}
require.NoError(f.SetMedusaDeplAvailable(ctx, medusaKey))
t.Log("check that dc1 was created")
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand Down Expand Up @@ -1956,8 +1953,6 @@ func applyClusterWithEncryptionOptionsExternalSecrets(t *testing.T, ctx context.
require.NoError(err, "failed to create K8ssandraCluster")

verifyFinalizerAdded(ctx, t, f, client.ObjectKey{Namespace: kc.Namespace, Name: kc.Name})
medusaKey := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), "dc1")}, K8sContext: f.DataPlaneContexts[0]}
require.NoError(f.SetMedusaDeplAvailable(ctx, medusaKey))
t.Log("check that dc1 was created")
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand Down Expand Up @@ -2487,8 +2482,6 @@ func injectContainersAndVolumes(t *testing.T, ctx context.Context, f *framework.

verifySystemReplicationAnnotationSet(ctx, t, f, kc)

// Create a Medusa deployment object and simulate it being available to make the k8c reconcile progress.
reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[0])
t.Log("check that dc1 was never created")
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand Down
59 changes: 1 addition & 58 deletions controllers/k8ssandra/medusa_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/k8ssandra/k8ssandra-operator/pkg/result"
"github.com/k8ssandra/k8ssandra-operator/pkg/secret"
"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -85,14 +84,6 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
cassandra.AddOrUpdateVolume(dcConfig, volume.Volume, volume.VolumeIndex, volume.Exists)
}

// Create the Medusa standalone pod
desiredMedusaStandalone := medusa.StandaloneMedusaDeployment(*medusaContainer, kc.SanitizedName(), dcConfig.SanitizedName(), dcNamespace, logger, kc.Spec.Medusa.ContainerImage)

// Add the volumes previously computed to the Medusa standalone pod
for _, volume := range volumes {
cassandra.AddOrUpdateVolumeToSpec(&desiredMedusaStandalone.Spec.Template, volume.Volume, volume.VolumeIndex, volume.Exists)
}

if !kc.Spec.UseExternalSecrets() {
cassandraUserSecretName := medusa.CassandraUserSecretName(medusaSpec, kc.SanitizedName())
cassandra.AddCqlUser(medusaSpec.CassandraUserSecretRef, dcConfig, cassandraUserSecretName)
Expand All @@ -105,38 +96,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
}
}

// Reconcile the Medusa standalone deployment
kcKey := utils.GetKey(kc)
desiredMedusaStandalone.SetLabels(labels.CleanedUpByLabels(kcKey))
recRes := reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *desiredMedusaStandalone)
switch {
case recRes.IsError():
return recRes
case recRes.IsRequeue():
return recRes
}

// Create and reconcile the Medusa service for the standalone deployment
medusaService := medusa.StandaloneMedusaService(dcConfig, medusaSpec, kc.SanitizedName(), dcNamespace, logger)
medusaService.SetLabels(labels.CleanedUpByLabels(kcKey))
recRes = reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *medusaService)
switch {
case recRes.IsError():
return recRes
case recRes.IsRequeue():
return recRes
}

// Check if the Medusa Standalone deployment is ready, and requeue if not
ready, err := r.isMedusaStandaloneReady(ctx, remoteClient, desiredMedusaStandalone)
if err != nil {
logger.Info("Failed to check if Medusa standalone deployment is ready", "error", err)
return result.Error(err)
}
if !ready {
logger.Info("Medusa standalone deployment is not ready yet")
return result.RequeueSoon(r.DefaultDelay)
}
// Create a cron job to purge Medusa backups
operatorNamespace := r.getOperatorNamespace()
purgeCronJob, err := medusa.PurgeCronJob(dcConfig, kc.SanitizedName(), operatorNamespace, logger)
Expand All @@ -145,7 +105,7 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
return result.Error(err)
}
purgeCronJob.SetLabels(labels.CleanedUpByLabels(kcKey))
recRes = reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *purgeCronJob)
recRes := reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *purgeCronJob)
switch {
case recRes.IsError():
return recRes
Expand All @@ -160,23 +120,6 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa(
return result.Continue()
}

// Check if the Medusa standalone deployment is ready
func (r *K8ssandraClusterReconciler) isMedusaStandaloneReady(ctx context.Context, remoteClient client.Client, desiredMedusaStandalone *appsv1.Deployment) (bool, error) {
// Get the medusa standalone deployment and check the rollout status
deplKey := utils.GetKey(desiredMedusaStandalone)
medusaStandalone := &appsv1.Deployment{}
if err := remoteClient.Get(context.Background(), deplKey, medusaStandalone); err != nil {
return false, err
}
// Check the conditions to see if the deployment has successfully rolled out
for _, c := range medusaStandalone.Status.Conditions {
if c.Type == appsv1.DeploymentAvailable {
return c.Status == corev1.ConditionTrue, nil // deployment is available
}
}
return false, nil // deployment condition not found
}

// Generate a secret for Medusa or use the existing one if provided in the spec
func (r *K8ssandraClusterReconciler) reconcileMedusaSecrets(
ctx context.Context,
Expand Down
97 changes: 0 additions & 97 deletions controllers/k8ssandra/medusa_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import (
medusaapi "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1"
cassandra "github.com/k8ssandra/k8ssandra-operator/pkg/cassandra"
"github.com/k8ssandra/k8ssandra-operator/pkg/images"
medusa "github.com/k8ssandra/k8ssandra-operator/pkg/medusa"
"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
"github.com/k8ssandra/k8ssandra-operator/test/framework"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -167,7 +165,6 @@ func createMultiDcClusterWithMedusa(t *testing.T, ctx context.Context, f *framew
require.NoError(err, "failed to create K8ssandraCluster")
verifyReplicatedSecretReconciled(ctx, t, f, kc)

reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[0])
t.Log("check that dc1 was created")
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand All @@ -176,28 +173,6 @@ func createMultiDcClusterWithMedusa(t *testing.T, ctx context.Context, f *framew
defaultPrefix := kc.Spec.Medusa.StorageProperties.Prefix
verifyConfigMap(require, ctx, f, namespace, defaultPrefix, defaultConcurrentTransfers)

t.Log("check that the standalone Medusa deployment was created in dc1")
medusaDeploymentKey1 := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaStandaloneDeploymentName(k8ssandraClusterName, "dc1")}, K8sContext: f.DataPlaneContexts[0]}
medusaDeployment1 := &appsv1.Deployment{}
require.Eventually(func() bool {
if err := f.Get(ctx, medusaDeploymentKey1, medusaDeployment1); err != nil {
return false
}
return true
}, timeout, interval)

require.True(f.ContainerHasEnvVar(medusaDeployment1.Spec.Template.Spec.Containers[0], "MEDUSA_RESOLVE_IP_ADDRESSES", "False"))

t.Log("check that the standalone Medusa service was created")
medusaServiceKey1 := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaServiceName(k8ssandraClusterName, "dc1")}, K8sContext: f.DataPlaneContexts[0]}
medusaService1 := &corev1.Service{}
require.Eventually(func() bool {
if err := f.Get(ctx, medusaServiceKey1, medusaService1); err != nil {
return false
}
return true
}, timeout, interval)

t.Log("update datacenter status to scaling up")
err = f.PatchDatacenterStatus(ctx, dc1Key, func(dc *cassdcapi.CassandraDatacenter) {
dc.SetCondition(cassdcapi.DatacenterCondition{
Expand Down Expand Up @@ -259,30 +234,9 @@ func createMultiDcClusterWithMedusa(t *testing.T, ctx context.Context, f *framew
return f.UpdateDatacenterGeneration(ctx, t, dc1Key)
}, timeout, interval, "failed to update dc1 generation")

reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc2", f.DataPlaneContexts[1])
t.Log("check that dc2 was created")
require.Eventually(f.DatacenterExists(ctx, dc2Key), timeout, interval)

t.Log("check that the standalone Medusa deployment was created in dc2")
medusaDeploymentKey2 := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaStandaloneDeploymentName(k8ssandraClusterName, "dc2")}, K8sContext: f.DataPlaneContexts[1]}
medusaDeployment2 := &appsv1.Deployment{}
require.Eventually(func() bool {
if err := f.Get(ctx, medusaDeploymentKey2, medusaDeployment2); err != nil {
return false
}
return true
}, timeout, interval)

t.Log("check that the standalone Medusa service was created in dc2")
medusaServiceKey2 := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: medusa.MedusaServiceName(k8ssandraClusterName, "dc2")}, K8sContext: f.DataPlaneContexts[1]}
medusaService2 := &corev1.Service{}
require.Eventually(func() bool {
if err := f.Get(ctx, medusaServiceKey2, medusaService2); err != nil {
return false
}
return true
}, timeout, interval)

t.Log("check that remote seeds are set on dc2")
dc2 = &cassdcapi.CassandraDatacenter{}
err = f.Get(ctx, dc2Key, dc2)
Expand Down Expand Up @@ -355,11 +309,6 @@ func createMultiDcClusterWithMedusa(t *testing.T, ctx context.Context, f *framew
err = f.DeleteK8ssandraCluster(ctx, client.ObjectKey{Namespace: namespace, Name: kc.Name}, timeout, interval)
require.NoError(err, "failed to delete K8ssandraCluster")
f.AssertObjectDoesNotExist(ctx, t, dc1Key, &cassdcapi.CassandraDatacenter{}, timeout, interval)
// Check that Medusa Standalone deployment and service were deleted
f.AssertObjectDoesNotExist(ctx, t, medusaDeploymentKey1, &appsv1.Deployment{}, timeout, interval)
f.AssertObjectDoesNotExist(ctx, t, medusaDeploymentKey2, &appsv1.Deployment{}, timeout, interval)
f.AssertObjectDoesNotExist(ctx, t, medusaServiceKey1, &corev1.Service{}, timeout, interval)
f.AssertObjectDoesNotExist(ctx, t, medusaServiceKey2, &corev1.Service{}, timeout, interval)
}

// Check that all the Medusa related objects have been created and are in the expected state.
Expand Down Expand Up @@ -397,47 +346,6 @@ func checkMedusaObjectsCompliance(t *testing.T, f *framework.Framework, dc *cass
}
}

func reconcileMedusaStandaloneDeployment(ctx context.Context, t *testing.T, f *framework.Framework, kc *api.K8ssandraCluster, dcName string, k8sContext string) {
t.Log("create Medusa Standalone deployment")

medusaDepl := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName),
Namespace: kc.Namespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName)},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName)},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName),
Image: "quay.io/k8ssandra/medusa:0.11.0",
},
},
},
},
},
}
medusaKey := framework.ClusterKey{NamespacedName: utils.GetKey(medusaDepl), K8sContext: k8sContext}
require.NoError(t, f.Create(ctx, medusaKey, medusaDepl))

actualMedusaDepl := &appsv1.Deployment{}
assert.Eventually(t, func() bool {
err := f.Get(ctx, medusaKey, actualMedusaDepl)
return err == nil
}, timeout, interval, "failed to get Medusa Deployment")

err := f.SetMedusaDeplAvailable(ctx, medusaKey)

require.NoError(t, err, "Failed to update Medusa Deployment status")
}

func createSingleDcClusterWithMedusaConfigRef(t *testing.T, ctx context.Context, f *framework.Framework, namespace string) {
require := require.New(t)

Expand Down Expand Up @@ -636,8 +544,6 @@ func createMultiDcClusterWithReplicatedSecrets(t *testing.T, ctx context.Context
verifySuperuserSecretCreated(ctx, t, f, kc)
verifyReplicatedSecretReconciled(ctx, t, f, kc)

reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[1])

// crate the first DC
dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[1]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand All @@ -648,7 +554,6 @@ func createMultiDcClusterWithReplicatedSecrets(t *testing.T, ctx context.Context
require.NoError(err, "failed to update dc1 status to ready")

// create the second DC
reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc2", f.DataPlaneContexts[2])
dc2Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc2"}, K8sContext: f.DataPlaneContexts[2]}
require.Eventually(f.DatacenterExists(ctx, dc2Key), timeout, interval)

Expand Down Expand Up @@ -709,8 +614,6 @@ func createSingleDcClusterWithManagementApiSecured(t *testing.T, ctx context.Con
require.NoError(f.Client.Create(ctx, kc))
verifyReplicatedSecretReconciled(ctx, t, f, kc)

reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[0])

dc1Key := framework.ClusterKey{NamespacedName: types.NamespacedName{Namespace: namespace, Name: "dc1"}, K8sContext: f.DataPlaneContexts[0]}
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)

Expand Down
11 changes: 10 additions & 1 deletion controllers/medusa/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,15 @@ func setupMedusaRestoreJobTestEnv(t *testing.T, ctx context.Context) *testutils.
}

for _, env := range testEnv.GetDataPlaneEnvTests() {
dataPlaneMgr, err := ctrl.NewManager(env.Config, ctrl.Options{Scheme: scheme.Scheme})
dataPlaneMgr, err := ctrl.NewManager(
env.Config,
ctrl.Options{
Scheme: scheme.Scheme,
Host: env.WebhookInstallOptions.LocalServingHost,
Port: env.WebhookInstallOptions.LocalServingPort,
CertDir: env.WebhookInstallOptions.LocalServingCertDir,
},
)
if err != nil {
return err
}
Expand All @@ -173,6 +181,7 @@ func setupMedusaRestoreJobTestEnv(t *testing.T, ctx context.Context) *testutils.
Scheme: scheme.Scheme,
ClientFactory: medusaRestoreClientFactory,
}).SetupWithManager(dataPlaneMgr)
secretswebhook.SetupSecretsInjectorWebhook(dataPlaneMgr)
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
Expand Down
44 changes: 0 additions & 44 deletions controllers/medusa/medusabackupjob_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ import (
"github.com/k8ssandra/k8ssandra-operator/pkg/images"
"github.com/k8ssandra/k8ssandra-operator/pkg/medusa"
"github.com/k8ssandra/k8ssandra-operator/pkg/shared"
"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
"github.com/k8ssandra/k8ssandra-operator/test/framework"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -89,7 +87,6 @@ func testMedusaBackupDatacenter(t *testing.T, ctx context.Context, f *framework.
require.NoError(err, "failed to create K8ssandraCluster")

reconcileReplicatedSecret(ctx, t, f, kc)
reconcileMedusaStandaloneDeployment(ctx, t, f, kc, "dc1", f.DataPlaneContexts[0])
t.Log("check that dc1 was created")
dc1Key := framework.NewClusterKey(f.DataPlaneContexts[0], namespace, "dc1")
require.Eventually(f.DatacenterExists(ctx, dc1Key), timeout, interval)
Expand Down Expand Up @@ -469,47 +466,6 @@ func verifyObjectDoesNotExist(ctx context.Context, t *testing.T, f *framework.Fr
}, timeout, interval, "failed to verify object does not exist", key)
}

func reconcileMedusaStandaloneDeployment(ctx context.Context, t *testing.T, f *framework.Framework, kc *k8ss.K8ssandraCluster, dcName string, k8sContext string) {
t.Logf("start reconcileMedusaStandaloneDeployment for dc %s", dcName)

medusaDepl := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName),
Namespace: kc.Namespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName)},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName)},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: medusa.MedusaStandaloneDeploymentName(kc.SanitizedName(), dcName),
Image: "quay.io/k8ssandra/medusa:0.11.0",
},
},
},
},
},
}
medusaKey := framework.ClusterKey{NamespacedName: utils.GetKey(medusaDepl), K8sContext: k8sContext}
require.NoError(t, f.Create(ctx, medusaKey, medusaDepl))

actualMedusaDepl := &appsv1.Deployment{}
assert.Eventually(t, func() bool {
err := f.Get(ctx, medusaKey, actualMedusaDepl)
return err == nil
}, timeout, interval, "failed to get Medusa Deployment")

err := f.SetMedusaDeplAvailable(ctx, medusaKey)

require.NoError(t, err, "Failed to update Medusa Deployment status")
}

func TestHumanize(t *testing.T) {
t.Run("humanizeTrivialSizes", humanizeTrivialSizes)
t.Run("humanizeArbitrarySizes", humanizeArbitrarySizes)
Expand Down
Loading
Loading