diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/SyncBucketUsageCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/SyncBucketUsageCmd.java new file mode 100644 index 000000000000..a1d33bc970c9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/SyncBucketUsageCmd.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.bucket; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.BucketResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.object.Bucket; + +@APICommand(name = "syncBucketUsage", description = "Synchronizes Bucket usage.", responseObject = BucketResponse.class, + responseView = ResponseView.Restricted, entityType = {Bucket.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class SyncBucketUsageCmd extends BaseCmd implements UserCmd { + + private static final String s_name = "syncbucketusageresponse"; + + @ACL(accessType = AccessType.OperateEntry) + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BucketResponse.class, + required = true, description = "The ID of the Bucket") + private Long id; + + public Long getId() { + return id; + } + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "bucket"; + } + + @Override + public long getEntityOwnerId() { + Bucket bucket = _entityMgr.findById(Bucket.class, getId()); + if (bucket != null) { + return bucket.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public Long getApiResourceId() { + return id; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.Bucket; + } + + @Override + public void execute() throws ConcurrentOperationException { + CallContext.current().setEventDetails("Bucket Id: " + this._uuidMgr.getUuid(Bucket.class, getId())); + Bucket bucket; + try { + bucket = _bucketService.syncBucketUsage(this, CallContext.current().getCallingAccount()); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Error while synchronizing bucket usage. " + e.getMessage()); + } + if (bucket != null) { + BucketResponse response = _responseGenerator.createBucketResponse(bucket); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to synchronize bucket usage"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java b/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java index 3dceab33403b..b0a28b7ddc15 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java @@ -21,6 +21,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.user.Account; import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; +import org.apache.cloudstack.api.command.user.bucket.SyncBucketUsageCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -107,5 +108,7 @@ public interface BucketApiService { boolean updateBucket(UpdateBucketCmd cmd, Account caller) throws ResourceAllocationException; + Bucket syncBucketUsage(SyncBucketUsageCmd cmd, Account caller); + void getBucketUsage(); } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index dd9fee50e639..6f183033be0d 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -402,6 +402,7 @@ import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; import org.apache.cloudstack.api.command.user.bucket.DeleteBucketCmd; import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; +import org.apache.cloudstack.api.command.user.bucket.SyncBucketUsageCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd; import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd; @@ -947,11 +948,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe static final String FOR_SYSTEMVMS = "forsystemvms"; static final ConfigKey vmPasswordLength = new ConfigKey<>("Advanced", Integer.class, "vm.password.length", "6", "Specifies the length of a randomly generated password", false); static final ConfigKey sshKeyLength = new ConfigKey<>("Advanced", Integer.class, "ssh.key.length", "2048", "Specifies custom SSH key length (bit)", true, ConfigKey.Scope.Global); - static final ConfigKey LicenseCheckInterval = new ConfigKey<>("Advanced", Integer.class, - "license.check.interval", "1", - "License check interval (days)", true); + static final ConfigKey licenseCheckInterval = new ConfigKey<>("Advanced", Integer.class, "license.check.interval", "1", "License check interval (days)", true); static final ConfigKey humanReadableSizes = new ConfigKey<>("Advanced", Boolean.class, "display.human.readable.sizes", "true", "Enables outputting human readable byte sizes to logs and usage records.", false, ConfigKey.Scope.Global); public static final ConfigKey customCsIdentifier = new ConfigKey<>("Advanced", String.class, "custom.cs.identifier", UUID.randomUUID().toString().split("-")[0].substring(4), "Custom identifier for the cloudstack installation", true, ConfigKey.Scope.Global); + public static final ConfigKey dbAactiveRestartThresholdPercent = new ConfigKey<>("Advanced", Double.class, "mold.db.pool.restart.threshold.percent", "0.98", "Restart the mold management service when active Cloud DB pool usage reaches this ratio of the maximum pool size. Values between 0 and 1 are recommended.", true, ConfigKey.Scope.Global); private static final VirtualMachine.Type []systemVmTypes = { VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.ConsoleProxy}; private static final List LIVE_MIGRATION_SUPPORTING_HYPERVISORS = List.of(HypervisorType.Hyperv, HypervisorType.KVM, HypervisorType.LXC, HypervisorType.Ovm, HypervisorType.Ovm3, HypervisorType.Simulator, HypervisorType.VMware, HypervisorType.XenServer); @@ -1240,8 +1240,8 @@ public boolean configure(final String name, final Map params) th _alertExecutor.scheduleAtFixedRate(new AlertPurgeTask(), alertPurgeInterval, alertPurgeInterval, TimeUnit.SECONDS); } - if(LicenseCheckInterval.value() > 0) { - _licenseExecutor.scheduleAtFixedRate(new LicenseCheckTask(), 0, LicenseCheckInterval.value(), TimeUnit.DAYS); + if(licenseCheckInterval.value() > 0) { + _licenseExecutor.scheduleAtFixedRate(new LicenseCheckTask(), 0, licenseCheckInterval.value(), TimeUnit.DAYS); } final String[] availableIds = TimeZone.getAvailableIDs(); @@ -6832,6 +6832,7 @@ public List> getCommands() { cmdList.add(UpdateBucketCmd.class); cmdList.add(DeleteBucketCmd.class); cmdList.add(ListBucketsCmd.class); + cmdList.add(SyncBucketUsageCmd.class); cmdList.add(LicenseCheckCmd.class); cmdList.add(ListHostRedfishDataCmd.class); @@ -6845,7 +6846,7 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {vmPasswordLength, sshKeyLength, humanReadableSizes, customCsIdentifier}; + return new ConfigKey[] {vmPasswordLength, sshKeyLength, humanReadableSizes, customCsIdentifier, dbAactiveRestartThresholdPercent}; } protected class EventPurgeTask extends ManagedContextRunnable { diff --git a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java index 8794767574db..5144e30f460e 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/object/BucketApiServiceImpl.java @@ -37,6 +37,7 @@ import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; +import org.apache.cloudstack.api.command.user.bucket.SyncBucketUsageCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.ConfigKey; @@ -86,7 +87,6 @@ public boolean configure(String name, Map params) throws Configu @Override public boolean start() { _executor.scheduleWithFixedDelay(new BucketUsageTask(), 60L, 3600L, TimeUnit.SECONDS); - _executor.scheduleWithFixedDelay(new BucketUsageCustomTask(), 60L, 60L, TimeUnit.SECONDS); return true; } @@ -295,32 +295,39 @@ public void getBucketUsage() { } } - private class BucketUsageCustomTask extends ManagedContextRunnable { - public BucketUsageCustomTask() { + @Override + public Bucket syncBucketUsage(SyncBucketUsageCmd cmd, Account caller) { + BucketVO bucket = _bucketDao.findById(cmd.getId()); + if (bucket == null) { + throw new InvalidParameterValueException("Unable to find bucket with ID: " + cmd.getId()); + } + _accountMgr.checkAccess(caller, null, true, bucket); + ObjectStoreVO objectStoreVO = _objectStoreDao.findById(bucket.getObjectStoreId()); + if (objectStoreVO == null) { + throw new InvalidParameterValueException("Unable to find object store with ID: " + bucket.getObjectStoreId()); } + ObjectStoreEntity objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object); + Map bucketSizes = objectStore.getAllBucketsUsage(); + updateBucketUsage(bucket, bucketSizes.getOrDefault(bucket.getName(), 0L), true); + return _bucketDao.findById(bucket.getId()); + } - @Override - protected void runInContext() { - try { - List objectStores = _objectStoreDao.listObjectStores(); - for(ObjectStoreVO objectStoreVO: objectStores) { - List buckets = _bucketDao.listByObjectStoreId(objectStoreVO.getId()); - if (buckets.isEmpty()) { - continue; - } - ObjectStoreEntity objectStore = (ObjectStoreEntity)_dataStoreMgr.getDataStore(objectStoreVO.getId(), DataStoreRole.Object); - Map bucketSizes = objectStore.getAllBucketsUsage(); - for(BucketVO bucket : buckets) { - Long size = bucketSizes.get(bucket.getName()); - if( size != null){ - bucket.setSize(size); - _bucketDao.update(bucket.getId(), bucket); - } - } - } - } catch (Exception e) { - logger.error("Error while fetching bucket usage", e); - } + private void updateBucketUsage(BucketVO bucket, long size, boolean updateStats) { + bucket.setSize(size); + _bucketDao.update(bucket.getId(), bucket); + + if (!updateStats) { + return; + } + + BucketStatisticsVO bucketStatisticsVO = _bucketStatisticsDao.findBy(bucket.getAccountId(), bucket.getId()); + if(bucketStatisticsVO != null) { + bucketStatisticsVO.setSize(size); + _bucketStatisticsDao.update(bucketStatisticsVO.getId(), bucketStatisticsVO); + } else { + bucketStatisticsVO = new BucketStatisticsVO(bucket.getAccountId(), bucket.getId()); + bucketStatisticsVO.setSize(size); + _bucketStatisticsDao.persist(bucketStatisticsVO); } } @@ -342,19 +349,7 @@ protected void runInContext() { for(BucketVO bucket : buckets) { Long size = bucketSizes.get(bucket.getName()); if( size != null){ - bucket.setSize(size); - _bucketDao.update(bucket.getId(), bucket); - - //Update Bucket Usage stats - BucketStatisticsVO bucketStatisticsVO = _bucketStatisticsDao.findBy(bucket.getAccountId(), bucket.getId()); - if(bucketStatisticsVO != null) { - bucketStatisticsVO.setSize(size); - _bucketStatisticsDao.update(bucketStatisticsVO.getId(), bucketStatisticsVO); - } else { - bucketStatisticsVO = new BucketStatisticsVO(bucket.getAccountId(), bucket.getId()); - bucketStatisticsVO.setSize(size); - _bucketStatisticsDao.persist(bucketStatisticsVO); - } + updateBucketUsage(bucket, size, true); } } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 7b9ad18ade09..4af540600a17 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -3699,6 +3699,7 @@ "message.success.release.dedicated.ipv4.subnet": "Successfully released dedicated IPv4 subnet", "message.success.remove.egress.rule": "Successfully removed egress rule", "message.success.remove.objectstore.objects": "Successfully removed selected object(s)", +"message.success.remove.objectstore.objects.count": "Removed {count} object(s).", "message.success.remove.objectstore.directory": "Successfully removed selected directory", "message.success.remove.firewall.rule": "Successfully removed firewall rule", "message.success.remove.instance.rule": "Successfully removed Instance from rule", diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index 47696cc1988a..2969723b525a 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -3698,6 +3698,7 @@ "message.success.release.dedicated.ipv4.subnet": "\uc804\uc6a9 IPv4 \uc11c\ube0c\ub137\uc744 \uc131\uacf5\uc801\uc73c\ub85c \ub9b4\ub9ac\uc2a4\ud588\uc2b5\ub2c8\ub2e4", "message.success.remove.egress.rule": "\uc1a1\uc2e0 \uaddc\uce59\uc744 \uc81c\uac70\ud588\uc2b5\ub2c8\ub2e4.", "message.success.remove.objectstore.objects": "\uc120\ud0dd\ud55c \uac1c\uccb4\ub97c \uc131\uacf5\uc801\uc73c\ub85c \uc81c\uac70\ud588\uc2b5\ub2c8\ub2e4.", +"message.success.remove.objectstore.objects.count": "\ucd1d {count}\uac1c \uac1d\uccb4\ub97c \uc0ad\uc81c\ud588\uc2b5\ub2c8\ub2e4.", "message.success.remove.objectstore.directory": "\uc120\ud0dd\ud55c \ub514\ub809\ud130\ub9ac\ub97c \uc131\uacf5\uc801\uc73c\ub85c \uc81c\uac70\ud588\uc2b5\ub2c8\ub2e4.", "message.success.remove.firewall.rule": "\ubc29\ud654\ubcbd \uaddc\uce59\uc744 \uc81c\uac70\ud588\uc2b5\ub2c8\ub2e4.", "message.success.remove.instance.rule": "\uaddc\uce59\uc5d0\uc11c \uac00\uc0c1\uba38\uc2e0\uc744 \uc81c\uac70\ud588\uc2b5\ub2c8\ub2e4.", diff --git a/ui/src/components/view/ObjectStoreBrowser.vue b/ui/src/components/view/ObjectStoreBrowser.vue index 15b07b07cb6b..86c5351d6653 100644 --- a/ui/src/components/view/ObjectStoreBrowser.vue +++ b/ui/src/components/view/ObjectStoreBrowser.vue @@ -16,6 +16,30 @@ // under the License. diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index ac7ee4423fd7..ddad00850499 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -673,7 +673,8 @@ :loading="loading" :tabs="$route.meta.tabs" :actions="actions" - @exec-action="handleDataViewAction" /> + @exec-action="handleDataViewAction" + @change-resource="resource = $event" />