Merge pull request #694 from apache/DLAB-1732
[DLAB-1732]: [Azure] Billing on local endpoint fixed
diff --git a/infrastructure-provisioning/src/general/conf/dlab.ini b/infrastructure-provisioning/src/general/conf/dlab.ini
index f732871..8ab5f9e 100644
--- a/infrastructure-provisioning/src/general/conf/dlab.ini
+++ b/infrastructure-provisioning/src/general/conf/dlab.ini
@@ -283,7 +283,7 @@
### Matplotlib version
matplotlib_version = 2.0.2
### JupyterLab image
-jupyterlab_image = odahu\/base-notebook:1.1.0-rc8
+jupyterlab_image = odahu\\/base-notebook:1.1.0-rc8
### Superset version
superset_version = 0.35.1
### GCS-connector version
diff --git a/infrastructure-provisioning/src/general/lib/os/debian/common_lib.py b/infrastructure-provisioning/src/general/lib/os/debian/common_lib.py
index 2ca635d..c70e9a9 100644
--- a/infrastructure-provisioning/src/general/lib/os/debian/common_lib.py
+++ b/infrastructure-provisioning/src/general/lib/os/debian/common_lib.py
@@ -30,30 +30,44 @@
def manage_pkg(command, environment, requisites):
try:
- allow = False
- counter = 0
- while not allow:
- if counter > 60:
+ attempt = 0
+ installed = False
+ while not installed:
+ print('Pkg installation attempt: {}'.format(attempt))
+ if attempt > 60:
print("Notebook is broken please recreate it.")
sys.exit(1)
else:
- print('Package manager is:')
- if environment == 'remote':
- if sudo('pgrep "^apt" -a && echo "busy" || echo "ready"') == 'busy':
- counter += 1
- time.sleep(10)
- else:
- allow = True
- sudo('apt-get {0} {1}'.format(command, requisites))
- elif environment == 'local':
- if local('sudo pgrep "^apt" -a && echo "busy" || echo "ready"', capture=True) == 'busy':
- counter += 1
- time.sleep(10)
- else:
- allow = True
- local('sudo apt-get {0} {1}'.format(command, requisites), capture=True)
- else:
- print('Wrong environment')
+ try:
+ allow = False
+ counter = 0
+ while not allow:
+ if counter > 60:
+ print("Notebook is broken please recreate it.")
+ sys.exit(1)
+ else:
+ print('Package manager is:')
+ if environment == 'remote':
+ if sudo('pgrep "^apt" -a && echo "busy" || echo "ready"') == 'busy':
+ counter += 1
+ time.sleep(10)
+ else:
+ allow = True
+ sudo('apt-get {0} {1}'.format(command, requisites))
+ elif environment == 'local':
+ if local('sudo pgrep "^apt" -a && echo "busy" || echo "ready"', capture=True) == 'busy':
+ counter += 1
+ time.sleep(10)
+ else:
+ allow = True
+ local('sudo apt-get {0} {1}'.format(command, requisites), capture=True)
+ else:
+ print('Wrong environment')
+ installed = True
+ except:
+ print("Will try to install with nex attempt.")
+ sudo('dpkg --configure -a')
+ attempt += 1
except:
sys.exit(1)
diff --git a/infrastructure-provisioning/src/general/lib/os/debian/ssn_lib.py b/infrastructure-provisioning/src/general/lib/os/debian/ssn_lib.py
index f828957..f4cda59 100644
--- a/infrastructure-provisioning/src/general/lib/os/debian/ssn_lib.py
+++ b/infrastructure-provisioning/src/general/lib/os/debian/ssn_lib.py
@@ -196,16 +196,16 @@
sudo('mv /tmp/ssn.yml ' + os.environ['ssn_dlab_path'] + 'conf/')
put('/root/templates/proxy_location_webapp_template.conf', '/tmp/proxy_location_webapp_template.conf')
sudo('mv /tmp/proxy_location_webapp_template.conf ' + os.environ['ssn_dlab_path'] + 'tmp/')
- if cloud_provider == 'gcp' or 'azure':
- conf_parameter_name = '--spring.config.location='
+ if cloud_provider == 'aws':
+ conf_parameter_name = '--spring.config.location={0}billing_app.yml --conf '.format(dlab_conf_dir)
with open('/root/templates/supervisor_svc.conf', 'r') as f:
text = f.read()
text = text.replace('WEB_CONF', dlab_conf_dir).replace('OS_USR', os_user)\
.replace('CONF_PARAMETER_NAME', conf_parameter_name)
with open('/root/templates/supervisor_svc.conf', 'w') as f:
f.write(text)
- elif cloud_provider == 'aws':
- conf_parameter_name = '--spring.config.location={0}billing_app.yml --conf '.format(dlab_conf_dir)
+ elif cloud_provider == 'gcp' or cloud_provider == 'azure':
+ conf_parameter_name = '--spring.config.location='
with open('/root/templates/supervisor_svc.conf', 'r') as f:
text = f.read()
text = text.replace('WEB_CONF', dlab_conf_dir).replace('OS_USR', os_user)\
diff --git a/infrastructure-provisioning/src/general/lib/os/fab.py b/infrastructure-provisioning/src/general/lib/os/fab.py
index e5fc30f..cd15d42 100644
--- a/infrastructure-provisioning/src/general/lib/os/fab.py
+++ b/infrastructure-provisioning/src/general/lib/os/fab.py
@@ -41,6 +41,7 @@
sudo('echo PATH=$PATH:/usr/local/bin/:/opt/spark/bin/ >> /etc/profile')
sudo('echo export PATH >> /etc/profile')
sudo('pip install -UI pip=={} --no-cache-dir'.format(os.environ['conf_pip_version']))
+ sudo('pip install --upgrade setuptools')
sudo('pip install -U {} --no-cache-dir'.format(requisites))
sudo('touch /home/{}/.ensure_dir/pip_path_added'.format(os.environ['conf_os_user']))
except:
diff --git a/infrastructure-provisioning/src/general/scripts/azure/ssn_configure.py b/infrastructure-provisioning/src/general/scripts/azure/ssn_configure.py
index ae50b95..db8f7cd 100644
--- a/infrastructure-provisioning/src/general/scripts/azure/ssn_configure.py
+++ b/infrastructure-provisioning/src/general/scripts/azure/ssn_configure.py
@@ -429,10 +429,10 @@
format(ssn_conf['instnace_ip'], ssn_conf['ssh_key_path'], os.environ['ssn_dlab_path'],
ssn_conf['dlab_ssh_user'], os.environ['conf_os_family'], os.environ['request_id'],
os.environ['conf_resource'], ssn_conf['service_base_name'], os.environ['conf_cloud_provider'],
- billing_enabled, azure_auth_path, os.environ['azure_offer_number'],
+ ssn_conf['billing_enabled'], ssn_conf['azure_auth_path'], os.environ['azure_offer_number'],
os.environ['azure_currency'], os.environ['azure_locale'], os.environ['azure_region_info'],
- ldap_login, tenant_id, datalake_application_id, datalake_store_name, json.dumps(cloud_params),
- subscription_id, os.environ['azure_validate_permission_scope'], ssn_conf['default_endpoint_name'],
+ ssn_conf['ldap_login'], ssn_conf['tenant_id'], ssn_conf['datalake_application_id'], ssn_conf['datalake_store_name'], json.dumps(cloud_params),
+ ssn_conf['subscription_id'], os.environ['azure_validate_permission_scope'], ssn_conf['default_endpoint_name'],
os.environ['keycloak_client_name'], os.environ['keycloak_client_secret'],
os.environ['keycloak_auth_server_url'])
local("~/scripts/{}.py {}".format('configure_ui', params))
diff --git a/infrastructure-provisioning/src/general/scripts/gcp/project_prepare.py b/infrastructure-provisioning/src/general/scripts/gcp/project_prepare.py
index 47b6cde..f9822a0 100644
--- a/infrastructure-provisioning/src/general/scripts/gcp/project_prepare.py
+++ b/infrastructure-provisioning/src/general/scripts/gcp/project_prepare.py
@@ -398,7 +398,8 @@
project_conf['tag_name']: project_conf['shared_bucket_name'],
"endpoint_tag": project_conf['endpoint_tag'],
os.environ['conf_billing_tag_key']: os.environ['conf_billing_tag_value'],
- "sbn": project_conf['service_base_name']}
+ "sbn": project_conf['service_base_name'],
+ "name": project_conf['shared_bucket_name']}
params = "--bucket_name {} --tags '{}'".format(project_conf['shared_bucket_name'],
json.dumps(project_conf['shared_bucket_tags']))
try:
@@ -412,7 +413,8 @@
"endpoint_tag": project_conf['endpoint_tag'],
os.environ['conf_billing_tag_key']: os.environ['conf_billing_tag_value'],
"sbn": project_conf['service_base_name'],
- "project_tag": project_conf['project_tag']}
+ "project_tag": project_conf['project_tag'],
+ "name": project_conf['bucket_name']}
params = "--bucket_name {} --tags '{}'".format(project_conf['bucket_name'],
json.dumps(project_conf['bucket_tags']))
diff --git a/infrastructure-provisioning/src/general/scripts/gcp/ssn_configure.py b/infrastructure-provisioning/src/general/scripts/gcp/ssn_configure.py
index ce37274..dd622d2 100644
--- a/infrastructure-provisioning/src/general/scripts/gcp/ssn_configure.py
+++ b/infrastructure-provisioning/src/general/scripts/gcp/ssn_configure.py
@@ -426,8 +426,8 @@
"--request_id {} --billing_dataset_name {} \
--resource {} --service_base_name {} --cloud_provider {} --default_endpoint_name {} " \
"--cloud_params '{}' --keycloak_client_id {} --keycloak_client_secret {} --keycloak_auth_server_url {}". \
- format(instance_hostname, ssn_conf['ssh_key_path'], os.environ['ssn_dlab_path'], ssn_conf['dlab_ssh_user'],
- os.environ['conf_os_family'], billing_enabled, os.environ['request_id'],
+ format(ssn_conf['instance_hostname'], ssn_conf['ssh_key_path'], os.environ['ssn_dlab_path'], ssn_conf['dlab_ssh_user'],
+ os.environ['conf_os_family'], ssn_conf['billing_enabled'], os.environ['request_id'],
os.environ['billing_dataset_name'], os.environ['conf_resource'],
ssn_conf['service_base_name'], os.environ['conf_cloud_provider'], ssn_conf['default_endpoint_name'],
json.dumps(cloud_params), os.environ['keycloak_client_name'], os.environ['keycloak_client_secret'],
diff --git a/infrastructure-provisioning/src/ssn/templates/ssn.yml b/infrastructure-provisioning/src/ssn/templates/ssn.yml
index fffa7d2..7b18d26 100644
--- a/infrastructure-provisioning/src/ssn/templates/ssn.yml
+++ b/infrastructure-provisioning/src/ssn/templates/ssn.yml
@@ -62,5 +62,10 @@
timeout: 3s
connectionTimeout: 3s
+billingService:
+ jerseyClient:
+ timeout: 4m
+ connectionTimeout: 3s
+
# Log out user on inactivity
inactiveUserTimeoutMillSec: 7200000
diff --git a/infrastructure-provisioning/terraform/bin/deploy/billing_aws.yml b/infrastructure-provisioning/terraform/bin/deploy/billing_aws.yml
index b3b5150..41add93 100644
--- a/infrastructure-provisioning/terraform/bin/deploy/billing_aws.yml
+++ b/infrastructure-provisioning/terraform/bin/deploy/billing_aws.yml
@@ -32,11 +32,6 @@
password: MONGO_PASSWORD
database: dlabdb
-scheduler:
-# Schedule is comma separated values of time in format hh[:mm[:ss]]. hh - in the 24-hour clock, at 8:15PM is 20:15.
- schedule: 0:00, 1:00, 2:00, 3:00, 4:00, 5:00, 6:00, 7:00, 8:00, 9:00, 10:00, 11:00, 12:00, 13:00, 14:00, 15:00, 16:00,
- 17:00, 18:00, 19:00, 20:00, 21:00, 22:00, 23:00
-
# Adapter for reading source data. Known types: file, s3file
adapterIn:
- type: s3file
diff --git a/infrastructure-provisioning/terraform/bin/deploy/provisioning.yml b/infrastructure-provisioning/terraform/bin/deploy/provisioning.yml
index 9d65fa2..abbbadf 100644
--- a/infrastructure-provisioning/terraform/bin/deploy/provisioning.yml
+++ b/infrastructure-provisioning/terraform/bin/deploy/provisioning.yml
@@ -67,6 +67,11 @@
timeout: 3s
connectionTimeout: 3s
+billingService:
+ jerseyClient:
+ timeout: 4m
+ connectionTimeout: 3s
+
# Log out user on inactivity
inactiveUserTimeoutMillSec: 7200000
@@ -76,7 +81,7 @@
responseDirectory: /opt/dlab/tmp
handlerDirectory: /opt/dlab/handlers
dockerLogDirectory: ${LOG_ROOT_DIR}
-warmupPollTimeout: 25s
+warmupPollTimeout: 2m
resourceStatusPollTimeout: 300m
keyLoaderPollTimeout: 30m
requestEnvStatusTimeout: 50s
diff --git a/infrastructure-provisioning/terraform/gcp/endpoint/provisioning.yml b/infrastructure-provisioning/terraform/gcp/endpoint/provisioning.yml
index fd5fc9b..6edb057 100644
--- a/infrastructure-provisioning/terraform/gcp/endpoint/provisioning.yml
+++ b/infrastructure-provisioning/terraform/gcp/endpoint/provisioning.yml
@@ -76,7 +76,7 @@
responseDirectory: /opt/dlab/tmp
handlerDirectory: /opt/dlab/handlers
dockerLogDirectory: ${LOG_ROOT_DIR}
-warmupPollTimeout: 25s
+warmupPollTimeout: 2m
resourceStatusPollTimeout: 300m
keyLoaderPollTimeout: 30m
requestEnvStatusTimeout: 50s
diff --git a/services/billing-aws/billing.yml b/services/billing-aws/billing.yml
index 9175c98..3b1943f 100644
--- a/services/billing-aws/billing.yml
+++ b/services/billing-aws/billing.yml
@@ -32,11 +32,6 @@
password: MONGO_PASSWORD
database: dlabdb
-scheduler:
-# Schedule is comma separated values of time in format hh[:mm[:ss]]. hh - in the 24-hour clock, at 8:15PM is 20:15.
- schedule: 0:00, 1:00, 2:00, 3:00, 4:00, 5:00, 6:00, 7:00, 8:00, 9:00, 10:00, 11:00, 12:00, 13:00, 14:00, 15:00, 16:00,
- 17:00, 18:00, 19:00, 20:00, 21:00, 22:00, 23:00
-
# Adapter for reading source data. Known types: file, s3file
adapterIn:
- type: s3file
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/BillingAwsApplication.java b/services/billing-aws/src/main/java/com/epam/dlab/BillingAwsApplication.java
index cede034..c878370 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/BillingAwsApplication.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/BillingAwsApplication.java
@@ -32,6 +32,6 @@
public static void main(String[] args) throws InitializationException {
SpringApplication.run(BillingAwsApplication.class, args);
- BillingScheduler.startScheduler(args);
+ BillingServiceImpl.startApplication(args);
}
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/BillingScheduler.java b/services/billing-aws/src/main/java/com/epam/dlab/BillingScheduler.java
deleted file mode 100644
index 7a7d5ff..0000000
--- a/services/billing-aws/src/main/java/com/epam/dlab/BillingScheduler.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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 com.epam.dlab;
-
-import com.epam.dlab.configuration.BillingToolConfiguration;
-import com.epam.dlab.configuration.BillingToolConfigurationFactory;
-import com.epam.dlab.configuration.SchedulerConfiguration;
-import com.epam.dlab.core.parser.ParserBase;
-import com.epam.dlab.exceptions.AdapterException;
-import com.epam.dlab.exceptions.DlabException;
-import com.epam.dlab.exceptions.InitializationException;
-import com.epam.dlab.exceptions.ParseException;
-import com.epam.dlab.util.ServiceUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Arrays;
-
-/**
- * Billing scheduler for loading billing report.
- */
-public class BillingScheduler implements Runnable {
- private static final Logger LOGGER = LoggerFactory.getLogger(BillingScheduler.class);
-
- /**
- * Timeout for check the schedule in milliseconds.
- */
- private static final long CHECK_TIMEOUT_MILLIS = 60000;
-
- /**
- * Billing scheduler instance.
- */
- private static BillingScheduler scheduler;
- private final boolean enabled;
- private final BillingToolConfiguration configuration;
-
- /**
- * Starts the scheduler for given configuration.
- *
- * @param filename the name of file for billing configuration.
- * @throws InitializationException
- */
- public static void start(String filename) throws InitializationException {
- if (scheduler == null) {
- scheduler = new BillingScheduler(filename);
- scheduler.thread.start();
- } else {
- LOGGER.debug("Billing scheduler already started");
- }
- }
-
- /**
- * Stops the scheduler.
- */
- public static void stop() {
- if (scheduler.thread != null) {
- LOGGER.debug("Billing scheduler will be stopped ...");
- synchronized (scheduler.thread) {
- scheduler.thread.interrupt();
- scheduler.thread = null;
- }
- LOGGER.info("Scheduler has been stopped");
- }
- }
-
-
- /**
- * Thread of the scheduler.
- */
- private Thread thread = new Thread(this, this.getClass().getSimpleName());
-
- /**
- * Name of configuration file.
- */
- private final String confFilename;
-
- /**
- * Current schedule.
- */
- private SchedulerConfiguration schedule;
-
- /**
- * Instantiate billing scheduler for given configuration.
- *
- * @param filename the name of file for billing configuration.
- * @throws InitializationException
- */
- public BillingScheduler(String filename) throws InitializationException {
- this.confFilename = filename;
- LOGGER.debug("Billing report configuration file: {}", filename);
- configuration = BillingToolConfigurationFactory.build(confFilename, BillingToolConfiguration.class);
- this.enabled = configuration.isBillingEnabled();
- setSchedule(configuration);
- }
-
- /**
- * Loads the billing report.
- *
- * @throws InitializationException
- * @throws AdapterException
- * @throws ParseException
- */
- private void load() throws InitializationException, AdapterException, ParseException {
- ParserBase parser = configuration.build();
- long time = schedule.getNearTime().getTimeInMillis();
- if (setSchedule(configuration)) {
- if (time != schedule.getNearTime().getTimeInMillis()) {
- LOGGER.info("Previous billing schedule has been canceled");
- return;
- }
- }
-
- LOGGER.info("Try to laod billing report for configuration: {}", configuration);
- parser.parse();
- if (!parser.getStatistics().isEmpty()) {
- LOGGER.info("Billing report parser statistics:");
- for (int i = 0; i < parser.getStatistics().size(); i++) {
- LOGGER.info(" {}", parser.getStatistics().get(i).toString());
- }
- }
- }
-
- /**
- * Read the schedule from configuration.
- *
- * @param configuration the billing configuration.
- * @return <b>true>/b> if new schedule was loaded, otherwise <b>false</b>.
- * @throws InitializationException
- */
- private boolean setSchedule(BillingToolConfiguration configuration) throws InitializationException {
- SchedulerConfiguration schedulerConfiguration = configuration.getScheduler();
- boolean isModified = false;
- if (schedulerConfiguration == null) {
- throw new InitializationException(String.format("Schedule of billing report in configuration file \"%s " +
- "not found", confFilename));
- }
- if (this.schedule == null) {
- isModified = true;
- LOGGER.debug("Billing report schedule: {}", schedulerConfiguration);
- } else {
- this.schedule.adjustStartTime();
- if (!schedulerConfiguration.equals(this.schedule)) {
- isModified = true;
- LOGGER.debug("New billing report schedule has been loaded: {}", schedulerConfiguration);
- }
- }
-
- try {
- this.schedule = new SchedulerConfiguration();
- this.schedule.setSchedule(schedulerConfiguration.getSchedule());
- this.schedule.build();
- } catch (Exception e) {
- throw new InitializationException("Cannot configure billing scheduler. " + e.getLocalizedMessage(), e);
- }
-
- return isModified;
- }
-
- @Override
- public void run() {
- if (enabled) {
- LOGGER.info("Billing scheduler has been started");
- long startTimeMillis = schedule.getNextTime().getTimeInMillis();
- long timeMillis;
- LOGGER.info("Billing report will be loaded at {}", schedule.getNextTime().getTime());
-
- try {
- while (!Thread.currentThread().isInterrupted()) {
- if (startTimeMillis <= System.currentTimeMillis()) {
- try {
- LOGGER.debug("Try to load billing report for schedule {}",
- schedule.getNextTime().getTime());
- load();
- } catch (InitializationException | AdapterException | ParseException e) {
- LOGGER.error("Error loading billing report: {}", e.getLocalizedMessage(), e);
- }
- startTimeMillis = schedule.getNextTime().getTimeInMillis();
- LOGGER.info("Billing report will be loaded at {}", schedule.getNextTime().getTime());
- } else {
- schedule.adjustStartTime();
- timeMillis = schedule.getNextTime().getTimeInMillis();
- if (startTimeMillis != timeMillis) {
- LOGGER.info("Billing report will be loaded at {}", schedule.getNextTime().getTime());
- startTimeMillis = timeMillis;
- }
- }
-
- try {
- timeMillis = startTimeMillis - System.currentTimeMillis();
- if (timeMillis > 0) {
- timeMillis = Math.min(CHECK_TIMEOUT_MILLIS, timeMillis);
- Thread.sleep(timeMillis);
- }
- } catch (InterruptedException e) {
- LOGGER.warn("Billing scheduler interrupted", e);
- Thread.currentThread().interrupt();
- }
- }
- } catch (Exception e) {
- LOGGER.error("Unhandled billing report error: {}", e.getLocalizedMessage(), e);
- }
- LOGGER.info("Scheduler has been stopped");
- } else {
- LOGGER.info("Billing scheduler is disabled");
- }
- }
-
-
- /**
- * Runs billing scheduler for given configuration file.
- *
- * @param args the arguments of command line.
- * @throws InitializationException
- */
- public static void startScheduler(String[] args) throws InitializationException {
- if (ServiceUtils.printAppVersion(BillingTool.class, args)) {
- return;
- }
-
- String confName = null;
- for (int i = 0; i < args.length; i++) {
- if (BillingTool.isKey("help", args[i])) {
- i++;
- Help.usage(i < args.length ? Arrays.copyOfRange(args, i, args.length) : null);
- return;
- } else if (BillingTool.isKey("conf", args[i])) {
- i++;
- if (i < args.length) {
- confName = args[i];
- } else {
- throw new InitializationException("Missing the name of configuration file");
- }
- }
- }
-
- if (confName == null) {
- Help.usage();
- throw new InitializationException("Missing arguments");
- }
-
- BillingTool.setLoggerLevel();
- try {
- start(confName);
- } catch (Exception e) {
- throw new DlabException("Billing scheduler failed", e);
- }
- }
-}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java b/services/billing-aws/src/main/java/com/epam/dlab/BillingService.java
similarity index 78%
rename from services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java
rename to services/billing-aws/src/main/java/com/epam/dlab/BillingService.java
index f72fa99..9b4d6db 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/BillingService.java
@@ -17,15 +17,12 @@
* under the License.
*/
-package com.epam.dlab.dao;
+package com.epam.dlab;
import com.epam.dlab.dto.billing.BillingData;
import java.util.List;
-public interface BillingDAO {
-
- List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products);
-
- List<BillingData> getBillingReport(List<String> dlabIds);
+public interface BillingService {
+ List<BillingData> getBillingData();
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/BillingServiceImpl.java b/services/billing-aws/src/main/java/com/epam/dlab/BillingServiceImpl.java
new file mode 100644
index 0000000..8ac6c48
--- /dev/null
+++ b/services/billing-aws/src/main/java/com/epam/dlab/BillingServiceImpl.java
@@ -0,0 +1,128 @@
+/*
+ * 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 com.epam.dlab;
+
+import com.epam.dlab.configuration.BillingToolConfiguration;
+import com.epam.dlab.configuration.BillingToolConfigurationFactory;
+import com.epam.dlab.core.parser.ParserBase;
+import com.epam.dlab.dto.billing.BillingData;
+import com.epam.dlab.exceptions.DlabException;
+import com.epam.dlab.exceptions.InitializationException;
+import com.epam.dlab.util.ServiceUtils;
+import org.bson.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.epam.dlab.model.aws.ReportLine.FIELD_COST;
+import static com.epam.dlab.model.aws.ReportLine.FIELD_CURRENCY_CODE;
+import static com.epam.dlab.model.aws.ReportLine.FIELD_DLAB_ID;
+import static com.epam.dlab.model.aws.ReportLine.FIELD_PRODUCT;
+import static com.epam.dlab.model.aws.ReportLine.FIELD_RESOURCE_TYPE;
+import static com.epam.dlab.model.aws.ReportLine.FIELD_USAGE_DATE;
+
+@Service
+public class BillingServiceImpl implements BillingService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BillingServiceImpl.class);
+ private static BillingToolConfiguration configuration;
+
+ public List<BillingData> getBillingData() {
+ try {
+ ParserBase parser = configuration.build();
+
+ LOGGER.info("Try to load billing report for configuration: {}", configuration);
+ List<BillingData> billingData = parser.parse()
+ .stream()
+ .map(this::toBillingData)
+ .collect(Collectors.toList());
+
+ if (!parser.getStatistics().isEmpty()) {
+ LOGGER.info("Billing report parser statistics:");
+ for (int i = 0; i < parser.getStatistics().size(); i++) {
+ LOGGER.info(" {}", parser.getStatistics().get(i).toString());
+ }
+ }
+
+ return billingData;
+ } catch (Exception e) {
+ LOGGER.error("Something went wrong ", e);
+ return Collections.emptyList();
+ }
+ }
+
+ private BillingData toBillingData(Document billingData) {
+ return BillingData.builder()
+ .tag(billingData.getString(FIELD_DLAB_ID).toLowerCase())
+ .usageDateFrom(Optional.ofNullable(billingData.getString(FIELD_USAGE_DATE)).map(LocalDate::parse).orElse(null))
+ .usageDateTo(Optional.ofNullable(billingData.getString(FIELD_USAGE_DATE)).map(LocalDate::parse).orElse(null))
+ .usageDate(billingData.getString(FIELD_USAGE_DATE))
+ .product(billingData.getString(FIELD_PRODUCT))
+ .usageType(billingData.getString(FIELD_RESOURCE_TYPE))
+ .cost(billingData.getDouble(FIELD_COST))
+ .currency(billingData.getString(FIELD_CURRENCY_CODE))
+ .build();
+ }
+
+ public static void initialize(String filename) throws InitializationException {
+ LOGGER.debug("Billing report configuration file: {}", filename);
+ configuration = BillingToolConfigurationFactory.build(filename, BillingToolConfiguration.class);
+ }
+
+ public static void startApplication(String[] args) throws InitializationException {
+ if (ServiceUtils.printAppVersion(BillingTool.class, args)) {
+ return;
+ }
+
+ String confName = null;
+ for (int i = 0; i < args.length; i++) {
+ if (BillingTool.isKey("help", args[i])) {
+ i++;
+ Help.usage(i < args.length ? Arrays.copyOfRange(args, i, args.length) : null);
+ return;
+ } else if (BillingTool.isKey("conf", args[i])) {
+ i++;
+ if (i < args.length) {
+ confName = args[i];
+ } else {
+ throw new InitializationException("Missing the name of configuration file");
+ }
+ }
+ }
+
+ if (confName == null) {
+ Help.usage();
+ throw new InitializationException("Missing arguments");
+ }
+
+ BillingTool.setLoggerLevel();
+ try {
+ initialize(confName);
+ } catch (Exception e) {
+ throw new DlabException("Billing scheduler failed", e);
+ }
+ }
+}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/BillingTool.java b/services/billing-aws/src/main/java/com/epam/dlab/BillingTool.java
index cf2b8d6..cde9d4e 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/BillingTool.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/BillingTool.java
@@ -19,24 +19,22 @@
package com.epam.dlab;
-import java.util.Arrays;
-
-import com.epam.dlab.exceptions.DlabException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
import com.epam.dlab.configuration.BillingToolConfiguration;
import com.epam.dlab.configuration.BillingToolConfigurationFactory;
import com.epam.dlab.core.parser.ParserBase;
import com.epam.dlab.exceptions.AdapterException;
+import com.epam.dlab.exceptions.DlabException;
import com.epam.dlab.exceptions.InitializationException;
import com.epam.dlab.exceptions.ParseException;
import com.epam.dlab.util.ServiceUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.LoggerContext;
+import java.util.Arrays;
/** Provides billing parser features.
*/
@@ -110,14 +108,14 @@
* @throws InitializationException
*/
public static void main(String[] args) throws InitializationException {
- if (ServiceUtils.printAppVersion(BillingScheduler.class, args)) {
+ if (ServiceUtils.printAppVersion(BillingServiceImpl.class, args)) {
return;
}
String confName = null;
String json = null;
-
- for(int i = 0; i < args.length; i++) {
+
+ for (int i = 0; i < args.length; i++) {
if (isKey("help", args[i])) {
i++;
Help.usage(i < args.length ? Arrays.copyOfRange(args, i, args.length) : null);
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/Help.java b/services/billing-aws/src/main/java/com/epam/dlab/Help.java
index 2a043c2..c2fe5c2 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/Help.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/Help.java
@@ -19,18 +19,17 @@
package com.epam.dlab;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang3.StringUtils;
-
import com.epam.dlab.core.BillingUtils;
import com.epam.dlab.core.ModuleType;
import com.epam.dlab.exceptions.InitializationException;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonTypeName;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/** Print help for billing tool.
*/
@@ -47,12 +46,12 @@
private static void printHelp(String resourceName, Map<String, String> substitute) throws InitializationException {
List<String> list = BillingUtils.getResourceAsList("/" + Help.class.getName() + "." + resourceName + ".txt");
String help = StringUtils.join(list, System.lineSeparator());
-
+
if (substitute == null) {
substitute = new HashMap<>();
}
- substitute.put("classname", BillingScheduler.class.getName());
-
+ substitute.put("classname", BillingServiceImpl.class.getName());
+
for (String key : substitute.keySet()) {
help = StringUtils.replace(help, "${" + key.toUpperCase() + "}", substitute.get(key));
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/configuration/BillingToolConfiguration.java b/services/billing-aws/src/main/java/com/epam/dlab/configuration/BillingToolConfiguration.java
index 803d232..420b9e0 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/configuration/BillingToolConfiguration.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/configuration/BillingToolConfiguration.java
@@ -77,13 +77,6 @@
private boolean billingEnabled;
/**
- * Working data file name of modules.
- */
- @Valid
- @JsonProperty
- private SchedulerConfiguration scheduler = null;
-
- /**
* Adapter for reading source data.
*/
@Valid
@@ -136,20 +129,6 @@
}
/**
- * Set the scheduler.
- */
- public void setScheduler(SchedulerConfiguration scheduler) {
- this.scheduler = scheduler;
- }
-
- /**
- * Return the scheduler.
- */
- public SchedulerConfiguration getScheduler() {
- return scheduler;
- }
-
- /**
* Set the adapter for reading source data.
*/
public void setAdapterIn(ImmutableList<AdapterBase> adapter) {
@@ -272,14 +251,6 @@
f.setModuleData(moduleData);
}
- if (scheduler != null) {
- try {
- scheduler.build();
- } catch (Exception e) {
- throw new InitializationException("Cannot configure billing scheduler. " + e.getLocalizedMessage(), e);
- }
- }
-
return parser.build(in, out, f);
}
@@ -295,7 +266,6 @@
public ToStringHelper toStringHelper(Object self) {
return MoreObjects.toStringHelper(self)
.add("moduleData", moduleData)
- .add("scheduler", scheduler)
.add("adapterIn", adapterIn)
.add("adapterOut", adapterOut)
.add("filter", filter)
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/configuration/SchedulerConfiguration.java b/services/billing-aws/src/main/java/com/epam/dlab/configuration/SchedulerConfiguration.java
deleted file mode 100644
index b0624d6..0000000
--- a/services/billing-aws/src/main/java/com/epam/dlab/configuration/SchedulerConfiguration.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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 com.epam.dlab.configuration;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.commons.lang3.StringUtils;
-
-import com.epam.dlab.exceptions.ParseException;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-
-/** Provides schedule time configuration.
- */
-public class SchedulerConfiguration {
-
- /** User's schedule. */
- @JsonProperty
- private String schedule = "12, 13:30:23, 18:34, 08:50, 7:80";
-
-
- /** Return the schedule of user.
- */
- public String getSchedule() {
- return schedule;
- }
-
- /** Set the schedule of user.
- */
- public void setSchedule(String schedule) {
- this.schedule = schedule;
- }
-
-
- /** Schedule. */
- private Map<String, Calendar> realSchedule = new TreeMap<>();
-
- /** Build the schedule from user' schedule.
- * @throws ParseException
- */
- public void build() throws ParseException {
- SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
- String [] unitArray = schedule.split(",");
- realSchedule.clear();
- for (int i = 0; i < unitArray.length; i++) {
- Calendar date = Calendar.getInstance();
- int [] time = getTime(unitArray[i]);
- try {
- df.parse(StringUtils.join(time, ':'));
- } catch (Exception e) {
- throw new ParseException("Cannot parse date " + unitArray[i] + ". " + e.getLocalizedMessage(), e);
- }
- date.clear();
- date.set(1, 1, 1, time[0], time[1], time[2]);
- realSchedule.put(df.format(date.getTime()), date);
- }
- adjustStartTime();
- }
-
- /** Return the schedule.
- */
- public Map<String, Calendar> getRealSchedule() {
- return realSchedule;
- }
-
- /** Return time array of user' schedule time.
- * @param time the time in format HH:mm:ss.
- * @throws ParseException
- */
- private int [] getTime(String time) throws ParseException {
- String [] timeString = time.trim().split(":");
- int [] timeInt = new int[3];
-
- for (int i = 0; i < timeInt.length; i++) {
- if (i < timeString.length) {
- try {
- timeInt[i] = Integer.parseInt(timeString[i]);
- } catch (Exception e) {
- throw new ParseException("Cannot parse date " + time + ". " + e.getLocalizedMessage(), e);
- }
- } else {
- timeInt[i] = 0;
- }
- }
-
- return timeInt;
- }
-
- /** Adjust the time in schedule for current time.
- */
- public void adjustStartTime() {
- Calendar now = Calendar.getInstance();
- for(String key : realSchedule.keySet()) {
- Calendar time = realSchedule.get(key);
- if (time.before(now)) {
- time.set(now.get(Calendar.YEAR),
- now.get(Calendar.MONTH),
- now.get(Calendar.DAY_OF_MONTH),
- time.get(Calendar.HOUR_OF_DAY),
- time.get(Calendar.MINUTE),
- time.get(Calendar.SECOND));
- if (time.before(now)) {
- time.add(Calendar.DAY_OF_MONTH, 1);
- }
- realSchedule.put(key, time);
- }
- }
- }
-
- /** Return the key of the next start time from the schedule.
- */
- public String getNextTimeKey() {
- long now = System.currentTimeMillis();
- String nextKey = null;
- long nextTime = -1;
-
- for(String key : realSchedule.keySet()) {
- long time = realSchedule.get(key).getTimeInMillis();
- if ((time >= now && time < nextTime) || nextTime == -1) {
- nextTime = time;
- nextKey = key;
- }
- }
- return nextKey;
- }
-
- /** Return the next start time from the schedule.
- */
- public Calendar getNextTime() {
- String key = getNextTimeKey();
- return (key == null ? null : realSchedule.get(key));
- }
-
- /** Return the key of the near start time from the schedule to the current time.
- */
- public String getNearTimeKey() {
- long now = System.currentTimeMillis();
- String nextKey = null;
- long nextTime = -1;
-
- for(String key : realSchedule.keySet()) {
- long time = Math.abs(now - realSchedule.get(key).getTimeInMillis());
- if (time < nextTime || nextTime == -1) {
- nextTime = time;
- nextKey = key;
- }
- }
- return nextKey;
- }
-
- /** Return the near start time from the schedule to the current time.
- */
- public Calendar getNearTime() {
- String key = getNearTimeKey();
- return (key == null ? null : realSchedule.get(key));
- }
-
- /** Returns a string representation of the object.
- * @param self the object to generate the string for (typically this), used only for its class name.
- */
- public ToStringHelper toStringHelper(Object self) {
- SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
- ToStringHelper helper = MoreObjects.toStringHelper(self);
- for(String key : realSchedule.keySet()) {
- Calendar time = realSchedule.get(key);
- helper.add(key, df.format(time.getTime()));
- }
- return helper;
- }
-
- @Override
- public String toString() {
- return toStringHelper(this)
- .toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SchedulerConfiguration)) return false;
-
- SchedulerConfiguration that = (SchedulerConfiguration) o;
-
- return getRealSchedule() != null ? getRealSchedule().keySet().equals(that.getRealSchedule().keySet())
- : that.getRealSchedule() == null;
- }
-
- @Override
- public int hashCode() {
- return getRealSchedule() != null ? getRealSchedule().keySet().hashCode() : 0;
- }
-}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/controller/BillingController.java b/services/billing-aws/src/main/java/com/epam/dlab/controller/BillingController.java
index 8f70083..deabf44 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/controller/BillingController.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/controller/BillingController.java
@@ -19,12 +19,11 @@
package com.epam.dlab.controller;
-import com.epam.dlab.dao.BillingDAO;
+import com.epam.dlab.BillingService;
import com.epam.dlab.dto.billing.BillingData;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -32,22 +31,14 @@
@RestController
public class BillingController {
- private final BillingDAO billingDAO;
+ private final BillingService billingService;
- public BillingController(BillingDAO billingDAO) {
- this.billingDAO = billingDAO;
+ public BillingController(BillingService billingService) {
+ this.billingService = billingService;
}
@GetMapping
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam List<String> dlabIds) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dlabIds), HttpStatus.OK);
- }
-
- @GetMapping("/report")
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam("date-start") String dateStart,
- @RequestParam("date-end") String dateEnd,
- @RequestParam("dlab-id") String dlabId,
- @RequestParam("product") List<String> products) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dateStart, dateEnd, dlabId, products), HttpStatus.OK);
+ public ResponseEntity<List<BillingData>> getBilling() {
+ return new ResponseEntity<>(billingService.getBillingData(), HttpStatus.OK);
}
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/core/AdapterBase.java b/services/billing-aws/src/main/java/com/epam/dlab/core/AdapterBase.java
index 1569530..475404d 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/core/AdapterBase.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/core/AdapterBase.java
@@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects.ToStringHelper;
+import org.bson.Document;
import java.util.List;
@@ -157,9 +158,10 @@
* Write the row of data to adapter.
*
* @param row the row of common format.
+ * @return
* @throws AdapterException
*/
- public abstract void writeRow(ReportLine row) throws AdapterException;
+ public abstract Document writeRow(ReportLine row) throws AdapterException;
@Override
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserBase.java b/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserBase.java
index f9f0eaa..bfd86bc 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserBase.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserBase.java
@@ -19,13 +19,6 @@
package com.epam.dlab.core.parser;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.validation.constraints.NotNull;
-
-import org.apache.commons.lang3.StringUtils;
-
import com.epam.dlab.core.AdapterBase;
import com.epam.dlab.core.FilterBase;
import com.epam.dlab.core.ModuleBase;
@@ -37,6 +30,12 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects.ToStringHelper;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.Document;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
/** Abstract module of parser.<br>
* See description of {@link ModuleBase} how to create your own parser.
@@ -234,13 +233,16 @@
* @throws InitializationException
*/
public abstract void initialize() throws InitializationException;
-
- /** Parse the source data to common format and write it to output adapter.
+
+ /**
+ * Parse the source data to common format and write it to output adapter.
+ *
+ * @return
* @throws InitializationException
* @throws AdapterException
* @throws ParseException
*/
- public abstract void parse() throws InitializationException, AdapterException, ParseException;
+ public abstract List<Document> parse() throws InitializationException, AdapterException, ParseException;
/** Build parser from given modules.
* @param adapterIn the adapter for reading source data.
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserByLine.java b/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserByLine.java
index 37f2070..d878cb9 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserByLine.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/core/parser/ParserByLine.java
@@ -27,10 +27,12 @@
import com.epam.dlab.exceptions.ParseException;
import com.epam.dlab.model.aws.ReportLine;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -156,11 +158,13 @@
/**
* Parse the source data to common format and write it to output adapter.
*
+ * @return list of billing data
* @throws InitializationException
* @throws AdapterException
* @throws ParseException
*/
- public void parse() throws InitializationException, AdapterException, ParseException {
+ public List<Document> parse() throws InitializationException, AdapterException, ParseException {
+ List<Document> billingData = new ArrayList<>();
try {
if (init()) {
String line;
@@ -211,14 +215,14 @@
if (getAggregate() != AggregateGranularity.NONE) {
getAggregator().append(reportLine);
} else {
- getAdapterOut().writeRow(reportLine);
+ billingData.add(getAdapterOut().writeRow(reportLine));
getCurrentStatistics().incrRowWritten();
}
}
if (getAggregate() != AggregateGranularity.NONE) {
for (int i = 0; i < getAggregator().size(); i++) {
- getAdapterOut().writeRow(getAggregator().get(i));
+ billingData.add(getAdapterOut().writeRow(getAggregator().get(i)));
getCurrentStatistics().incrRowWritten();
}
}
@@ -255,5 +259,6 @@
if (getCurrentStatistics() != null) {
getCurrentStatistics().stop();
}
+ return billingData;
}
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/dao/impl/BillingDAOImpl.java b/services/billing-aws/src/main/java/com/epam/dlab/dao/impl/BillingDAOImpl.java
deleted file mode 100644
index b0ff9f1..0000000
--- a/services/billing-aws/src/main/java/com/epam/dlab/dao/impl/BillingDAOImpl.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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 com.epam.dlab.dao.impl;
-
-import com.epam.dlab.dao.BillingDAO;
-import com.epam.dlab.dto.billing.BillingData;
-import com.epam.dlab.exceptions.DlabException;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.bson.Document;
-import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.aggregation.Aggregation;
-import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
-import org.springframework.data.mongodb.core.aggregation.GroupOperation;
-import org.springframework.data.mongodb.core.aggregation.MatchOperation;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import static com.epam.dlab.model.aws.ReportLine.FIELD_COST;
-import static com.epam.dlab.model.aws.ReportLine.FIELD_CURRENCY_CODE;
-import static com.epam.dlab.model.aws.ReportLine.FIELD_DLAB_ID;
-import static com.epam.dlab.model.aws.ReportLine.FIELD_PRODUCT;
-import static com.epam.dlab.model.aws.ReportLine.FIELD_RESOURCE_TYPE;
-import static com.epam.dlab.model.aws.ReportLine.FIELD_USAGE_DATE;
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
-
-@Component
-@Slf4j
-public class BillingDAOImpl implements BillingDAO {
- private final MongoTemplate mongoTemplate;
-
- public BillingDAOImpl(MongoTemplate mongoTemplate) {
- this.mongoTemplate = mongoTemplate;
- }
-
- @Override
- public List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products) {
- try {
- List<AggregationOperation> aggregationOperations = new ArrayList<>();
- aggregationOperations.add(Aggregation.match(Criteria.where(FIELD_DLAB_ID).regex(dlabId, "i")));
- if (!products.isEmpty()) {
- aggregationOperations.add(Aggregation.match(Criteria.where(FIELD_PRODUCT).in(products)));
- }
- getMatchCriteria(dateStart, Criteria.where(FIELD_USAGE_DATE).gte(dateStart))
- .ifPresent(aggregationOperations::add);
- getMatchCriteria(dateEnd, Criteria.where(FIELD_USAGE_DATE).lte(dateEnd))
- .ifPresent(aggregationOperations::add);
- aggregationOperations.add(getGroupOperation());
-
- Aggregation aggregation = newAggregation(aggregationOperations);
-
- return mongoTemplate.aggregate(aggregation, "billing", Document.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- @Override
- public List<BillingData> getBillingReport(List<String> dlabIds) {
- try {
- GroupOperation groupOperation = getGroupOperation();
- MatchOperation matchOperation = Aggregation.match(Criteria.where("dlab_id").in(dlabIds));
- Aggregation aggregation = newAggregation(matchOperation, groupOperation);
-
- return mongoTemplate.aggregate(aggregation, "billing", Document.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- private GroupOperation getGroupOperation() {
- return group(FIELD_PRODUCT, FIELD_CURRENCY_CODE, FIELD_RESOURCE_TYPE, FIELD_DLAB_ID)
- .min(FIELD_USAGE_DATE).as("from")
- .max(FIELD_USAGE_DATE).as("to")
- .sum(FIELD_COST).as(FIELD_COST);
- }
-
- private Optional<MatchOperation> getMatchCriteria(String dateStart, Criteria criteria) {
- return Optional.ofNullable(dateStart)
- .filter(StringUtils::isNotEmpty)
- .map(date -> Aggregation.match(criteria));
- }
-
- private BillingData toBillingData(Document billingData) {
- return BillingData.builder()
- .tag(billingData.getString(FIELD_DLAB_ID))
- .usageDateFrom(Optional.ofNullable(billingData.getString("from")).map(LocalDate::parse).orElse(null))
- .usageDateTo(Optional.ofNullable(billingData.getString("to")).map(LocalDate::parse).orElse(null))
- .product(billingData.getString(FIELD_PRODUCT))
- .usageType(billingData.getString(FIELD_RESOURCE_TYPE))
- .cost(BigDecimal.valueOf(billingData.getDouble(FIELD_COST)).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue())
- .currency(billingData.getString(FIELD_CURRENCY_CODE))
- .build();
- }
-}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterConsole.java b/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterConsole.java
index 59c866d..3bffa79 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterConsole.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterConsole.java
@@ -25,6 +25,7 @@
import com.epam.dlab.model.aws.ReportLine;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonTypeName;
+import org.bson.Document;
import java.util.List;
@@ -84,7 +85,8 @@
}
@Override
- public void writeRow(ReportLine row) throws AdapterException {
+ public Document writeRow(ReportLine row) throws AdapterException {
System.out.println(CommonFormat.rowToString(row));
+ return null;
}
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterFile.java b/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterFile.java
index 7fb38f3..dd256eb 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterFile.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/module/AdapterFile.java
@@ -19,15 +19,6 @@
package com.epam.dlab.module;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.List;
-
-import javax.validation.constraints.NotNull;
-
import com.epam.dlab.core.AdapterBase;
import com.epam.dlab.core.parser.CommonFormat;
import com.epam.dlab.exceptions.AdapterException;
@@ -37,6 +28,15 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.base.MoreObjects.ToStringHelper;
+import org.bson.Document;
+
+import javax.validation.constraints.NotNull;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
/** The adapter for file system.
*/
@@ -137,15 +137,16 @@
throw new AdapterException("Cannot write file " + file + ". " + e.getLocalizedMessage(), e);
}
}
-
+
@Override
- public void writeRow(ReportLine row) throws AdapterException {
+ public Document writeRow(ReportLine row) throws AdapterException {
try {
writer.write(CommonFormat.rowToString(row));
writer.write(System.lineSeparator());
} catch (IOException e) {
throw new AdapterException("Cannot write file " + file + ". " + e.getLocalizedMessage(), e);
}
+ return null;
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/module/aws/AdapterS3File.java b/services/billing-aws/src/main/java/com/epam/dlab/module/aws/AdapterS3File.java
index 9dc7e07..0579063 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/module/aws/AdapterS3File.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/module/aws/AdapterS3File.java
@@ -33,6 +33,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.base.MoreObjects.ToStringHelper;
+import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -308,7 +309,7 @@
}
@Override
- public void writeRow(ReportLine row) throws AdapterException {
+ public Document writeRow(ReportLine row) throws AdapterException {
throw new AdapterException("Unimplemented method.");
}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/mongo/AdapterMongoDb.java b/services/billing-aws/src/main/java/com/epam/dlab/mongo/AdapterMongoDb.java
index 994522d..db92a80 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/mongo/AdapterMongoDb.java
+++ b/services/billing-aws/src/main/java/com/epam/dlab/mongo/AdapterMongoDb.java
@@ -219,7 +219,7 @@
}
@Override
- public void writeRow(ReportLine row) throws AdapterException {
+ public Document writeRow(ReportLine row) throws AdapterException {
Document document;
try {
document = resourceTypeDAO.transform(row);
@@ -227,20 +227,21 @@
throw new AdapterException("Cannot transform report line. " + e.getLocalizedMessage(), e);
}
- usageDateList.append(row.getUsageDate());
- if (upsert) {
- buffer.add(document);
- if (buffer.size() >= bufferSize) {
- connection.upsertRows(collection, buffer, usageDateList);
- }
- } else if (bufferSize > 0) {
- buffer.add(document);
- if (buffer.size() >= bufferSize) {
- connection.insertRows(collection, buffer);
- }
- } else {
- connection.insertOne(collection, document);
- }
+// usageDateList.append(row.getUsageDate());
+// if (upsert) {
+// buffer.add(document);
+// if (buffer.size() >= bufferSize) {
+// connection.upsertRows(collection, buffer, usageDateList);
+// }
+// } else if (bufferSize > 0) {
+// buffer.add(document);
+// if (buffer.size() >= bufferSize) {
+// connection.insertRows(collection, buffer);
+// }
+// } else {
+// connection.insertOne(collection, document);
+// }
+ return document;
}
/**
diff --git a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/BillingSchedulerAzure.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/BillingSchedulerAzure.java
deleted file mode 100644
index f81fe2e..0000000
--- a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/BillingSchedulerAzure.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * 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 com.epam.dlab.billing.azure;
-
-import com.epam.dlab.MongoKeyWords;
-import com.epam.dlab.billing.azure.config.AzureAuthFile;
-import com.epam.dlab.billing.azure.config.BillingConfigurationAzure;
-import com.epam.dlab.billing.azure.model.AzureDailyResourceInvoice;
-import com.epam.dlab.billing.azure.model.BillingPeriod;
-import com.epam.dlab.exceptions.DlabException;
-import com.epam.dlab.util.mongo.modules.IsoDateModule;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.mongodb.BasicDBObject;
-import com.mongodb.client.model.Filters;
-import com.mongodb.client.model.UpdateOptions;
-import com.mongodb.client.result.UpdateResult;
-import lombok.extern.slf4j.Slf4j;
-import org.bson.Document;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.PostConstruct;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Component
-public class BillingSchedulerAzure {
- private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
- private BillingConfigurationAzure billingConfigurationAzure;
- private MongoDbBillingClient mongoDbBillingClient;
-
- @Autowired
- public BillingSchedulerAzure(BillingConfigurationAzure configuration) throws IOException {
- billingConfigurationAzure = configuration;
- Path path = Paths.get(billingConfigurationAzure.getAuthenticationFile());
-
- if (path.toFile().exists()) {
- log.info("Read and override configs using auth file");
- try {
- AzureAuthFile azureAuthFile = new ObjectMapper().readValue(path.toFile(), AzureAuthFile.class);
- this.billingConfigurationAzure.setClientId(azureAuthFile.getClientId());
- this.billingConfigurationAzure.setClientSecret(azureAuthFile.getClientSecret());
- this.billingConfigurationAzure.setTenantId(azureAuthFile.getTenantId());
- this.billingConfigurationAzure.setSubscriptionId(azureAuthFile.getSubscriptionId());
- } catch (IOException e) {
- log.error("Cannot read configuration file", e);
- throw e;
- }
- log.info("Configs from auth file are used");
- } else {
- log.info("Configs from yml file are used");
- }
-
- this.mongoDbBillingClient = new MongoDbBillingClient
- (billingConfigurationAzure.getAggregationOutputMongoDataSource().getHost(),
- billingConfigurationAzure.getAggregationOutputMongoDataSource().getPort(),
- billingConfigurationAzure.getAggregationOutputMongoDataSource().getDatabase(),
- billingConfigurationAzure.getAggregationOutputMongoDataSource().getUsername(),
- billingConfigurationAzure.getAggregationOutputMongoDataSource().getPassword());
- }
-
- @PostConstruct
- public void start() {
- if (billingConfigurationAzure.isBillingEnabled()) {
- executorService.scheduleWithFixedDelay(new CalculateBilling(billingConfigurationAzure,
- mongoDbBillingClient), billingConfigurationAzure.getInitialDelay(),
- billingConfigurationAzure.getPeriod(), TimeUnit.MINUTES);
- } else {
- log.info("======Billing is disabled======");
- }
- }
-
- public void stop() {
- try {
- log.info("Stopping Azure billing scheduler");
- if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
- log.error("Force shut down");
- executorService.shutdownNow();
- }
- mongoDbBillingClient.getClient().close();
- } catch (InterruptedException e) {
- executorService.shutdownNow();
- mongoDbBillingClient.getClient().close();
- Thread.currentThread().interrupt();
- }
- }
-
-
- @Slf4j
- private static class CalculateBilling implements Runnable {
- private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern
- ("yyyy-MM-dd'T'HH:mm:ss.SSS'Z");
- private static final String SCHEDULER_ID = "azureBillingScheduler";
- private BillingConfigurationAzure billingConfigurationAzure;
- private MongoDbBillingClient client;
- private ObjectMapper objectMapper = new ObjectMapper().registerModule(new IsoDateModule());
-
-
- public CalculateBilling(BillingConfigurationAzure billingConfigurationAzure, MongoDbBillingClient client) {
- this.billingConfigurationAzure = billingConfigurationAzure;
- this.client = client;
- }
-
- @Override
- public void run() {
- try {
- BillingPeriod billingPeriod = getBillingPeriod();
- DateTime currentTime = new DateTime().withZone(DateTimeZone.UTC);
- if (billingPeriod == null) {
- saveBillingPeriod(initialSchedulerInfo(currentTime));
- } else {
- log.info("Billing period from db is {}", billingPeriod);
-
- if (shouldTriggerJobByTime(currentTime, billingPeriod)) {
- boolean hasNew = run(billingPeriod);
- updateBillingPeriod(billingPeriod, currentTime, hasNew);
- }
- }
- } catch (RuntimeException e) {
- log.error("Cannot update billing information", e);
- }
- }
-
- private BillingPeriod initialSchedulerInfo(DateTime currentTime) {
-
- BillingPeriod initialBillingPeriod = new BillingPeriod();
- initialBillingPeriod.setFrom(currentTime.minusDays(2).toDateMidnight().toDate());
- initialBillingPeriod.setTo(currentTime.toDateMidnight().toDate());
-
- log.info("Initial scheduler info {}", initialBillingPeriod);
-
- return initialBillingPeriod;
-
- }
-
- private boolean shouldTriggerJobByTime(DateTime currentTime, BillingPeriod billingPeriod) {
-
- DateTime dateTimeToFromBillingPeriod = new DateTime(billingPeriod.getTo()).withZone(DateTimeZone.UTC);
-
- log.info("Comparing current time[{}, {}] and from scheduler info [{}, {}]", currentTime,
- currentTime.toDateMidnight(),
- dateTimeToFromBillingPeriod, dateTimeToFromBillingPeriod.toDateMidnight());
-
- if (currentTime.toDateMidnight().isAfter(dateTimeToFromBillingPeriod.toDateMidnight())
- || currentTime.toDateMidnight().isEqual(dateTimeToFromBillingPeriod.toDateMidnight())) {
- log.info("Should trigger the job by time");
- return true;
- }
-
- log.info("Should not trigger the job by time");
- return false;
- }
-
- private boolean run(BillingPeriod billingPeriod) {
- AzureInvoiceCalculationService azureInvoiceCalculationService
- = new AzureInvoiceCalculationService(billingConfigurationAzure);
-
- List<AzureDailyResourceInvoice> dailyInvoices = azureInvoiceCalculationService.generateInvoiceData(
- DATE_TIME_FORMATTER.print(new DateTime(billingPeriod.getFrom()).withZone(DateTimeZone.UTC)),
- DATE_TIME_FORMATTER.print(new DateTime(billingPeriod.getTo()).withZone(DateTimeZone.UTC)));
-
- if (!dailyInvoices.isEmpty()) {
- client.getDatabase().getCollection(MongoKeyWords.BILLING_DETAILS)
- .insertMany(dailyInvoices.stream().map(AzureDailyResourceInvoice::to)
- .collect(Collectors.toList()));
- return true;
- } else {
- log.warn("Daily invoices is empty for period {}", billingPeriod);
- return false;
- }
- }
-
- private void updateBillingPeriod(BillingPeriod billingPeriod, DateTime currentTime, boolean updates) {
-
- try {
- client.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER_HISTORY).insertOne(
- Document.parse(objectMapper.writeValueAsString(billingPeriod)).append("updates", updates));
- log.debug("History of billing periods is updated with {}",
- objectMapper.writeValueAsString(billingPeriod));
- } catch (JsonProcessingException e) {
- log.error("Cannot update history of billing periods", e);
-
- }
-
- billingPeriod.setFrom(billingPeriod.getTo());
-
- if (new DateTime(billingPeriod.getFrom()).withZone(DateTimeZone.UTC).toDateMidnight()
- .isEqual(currentTime.toDateMidnight())) {
-
- log.info("Setting billing to one day later");
- billingPeriod.setTo(currentTime.plusDays(1).toDateMidnight().toDate());
-
- } else {
- billingPeriod.setTo(currentTime.toDateMidnight().toDate());
- }
-
- saveBillingPeriod(billingPeriod);
- }
-
- private boolean saveBillingPeriod(BillingPeriod billingPeriod) {
- log.debug("Saving billing period {}", billingPeriod);
-
- try {
- UpdateResult updateResult = client.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER)
- .updateMany(Filters.eq(MongoKeyWords.MONGO_ID, SCHEDULER_ID),
- new BasicDBObject("$set",
- Document.parse(objectMapper.writeValueAsString(billingPeriod))
- .append(MongoKeyWords.MONGO_ID, SCHEDULER_ID))
- , new UpdateOptions().upsert(true)
- );
-
- log.debug("Billing period save operation result is {}", updateResult);
- return true;
- } catch (JsonProcessingException e) {
- log.error("Cannot save billing period", e);
- }
-
- return false;
- }
-
- private BillingPeriod getBillingPeriod() {
- log.debug("Get billing period");
-
- try {
- Document document = client.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER)
- .find(Filters.eq(MongoKeyWords.MONGO_ID, SCHEDULER_ID)).first();
-
- log.debug("Retrieved billing period document {}", document);
- if (document != null) {
- return objectMapper.readValue(document.toJson(), BillingPeriod.class);
- }
-
- return null;
-
- } catch (IOException e) {
- log.error("Cannot save billing period", e);
- throw new DlabException("Cannot parse string", e);
- }
- }
- }
-}
diff --git a/services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingService.java
similarity index 78%
copy from services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java
copy to services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingService.java
index f72fa99..d432337 100644
--- a/services/billing-aws/src/main/java/com/epam/dlab/dao/BillingDAO.java
+++ b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingService.java
@@ -17,15 +17,12 @@
* under the License.
*/
-package com.epam.dlab.dao;
+package com.epam.dlab.billing.azure;
import com.epam.dlab.dto.billing.BillingData;
import java.util.List;
-public interface BillingDAO {
-
- List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products);
-
- List<BillingData> getBillingReport(List<String> dlabIds);
+public interface CalculateBillingService {
+ List<BillingData> getBillingData();
}
diff --git a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingServiceImpl.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingServiceImpl.java
new file mode 100644
index 0000000..3b3d60b
--- /dev/null
+++ b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/CalculateBillingServiceImpl.java
@@ -0,0 +1,245 @@
+/*
+ * 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 com.epam.dlab.billing.azure;
+
+import com.epam.dlab.MongoKeyWords;
+import com.epam.dlab.billing.azure.config.AzureAuthFile;
+import com.epam.dlab.billing.azure.config.BillingConfigurationAzure;
+import com.epam.dlab.billing.azure.model.AzureDailyResourceInvoice;
+import com.epam.dlab.billing.azure.model.BillingPeriod;
+import com.epam.dlab.dto.billing.BillingData;
+import com.epam.dlab.exceptions.DlabException;
+import com.epam.dlab.util.mongo.modules.IsoDateModule;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.BasicDBObject;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.UpdateOptions;
+import com.mongodb.client.result.UpdateResult;
+import lombok.extern.slf4j.Slf4j;
+import org.bson.Document;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class CalculateBillingServiceImpl implements CalculateBillingService {
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z");
+ private static final String SCHEDULER_ID = "azureBillingScheduler";
+ private final BillingConfigurationAzure billingConfigurationAzure;
+ private final MongoDbBillingClient mongoDbBillingClient;
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ public CalculateBillingServiceImpl(BillingConfigurationAzure configuration) throws IOException {
+ billingConfigurationAzure = configuration;
+ objectMapper = new ObjectMapper().registerModule(new IsoDateModule());
+ Path path = Paths.get(billingConfigurationAzure.getAuthenticationFile());
+
+ if (path.toFile().exists()) {
+ log.info("Read and override configs using auth file");
+ try {
+ AzureAuthFile azureAuthFile = new ObjectMapper().readValue(path.toFile(), AzureAuthFile.class);
+ this.billingConfigurationAzure.setClientId(azureAuthFile.getClientId());
+ this.billingConfigurationAzure.setClientSecret(azureAuthFile.getClientSecret());
+ this.billingConfigurationAzure.setTenantId(azureAuthFile.getTenantId());
+ this.billingConfigurationAzure.setSubscriptionId(azureAuthFile.getSubscriptionId());
+ } catch (IOException e) {
+ log.error("Cannot read configuration file", e);
+ throw e;
+ }
+ log.info("Configs from auth file are used");
+ } else {
+ log.info("Configs from yml file are used");
+ }
+
+ this.mongoDbBillingClient = new MongoDbBillingClient
+ (billingConfigurationAzure.getAggregationOutputMongoDataSource().getHost(),
+ billingConfigurationAzure.getAggregationOutputMongoDataSource().getPort(),
+ billingConfigurationAzure.getAggregationOutputMongoDataSource().getDatabase(),
+ billingConfigurationAzure.getAggregationOutputMongoDataSource().getUsername(),
+ billingConfigurationAzure.getAggregationOutputMongoDataSource().getPassword());
+ }
+
+ @Override
+ public List<BillingData> getBillingData() {
+ try {
+ BillingPeriod billingPeriod = getBillingPeriod();
+ DateTime currentTime = new DateTime().withZone(DateTimeZone.UTC);
+ if (billingPeriod == null) {
+ saveBillingPeriod(initialSchedulerInfo(currentTime));
+ } else {
+ log.info("Billing period from db is {}", billingPeriod);
+
+ if (shouldTriggerJobByTime(currentTime, billingPeriod)) {
+ List<BillingData> billingData = getBillingData(billingPeriod);
+ boolean hasNew = !billingData.isEmpty();
+ updateBillingPeriod(billingPeriod, currentTime, hasNew);
+ return billingData;
+ }
+ }
+ } catch (RuntimeException e) {
+ log.error("Cannot update billing information", e);
+ }
+ return Collections.emptyList();
+ }
+
+ private BillingPeriod initialSchedulerInfo(DateTime currentTime) {
+
+ BillingPeriod initialBillingPeriod = new BillingPeriod();
+ initialBillingPeriod.setFrom(currentTime.minusDays(2).toDateMidnight().toDate());
+ initialBillingPeriod.setTo(currentTime.toDateMidnight().toDate());
+
+ log.info("Initial scheduler info {}", initialBillingPeriod);
+
+ return initialBillingPeriod;
+
+ }
+
+ private boolean shouldTriggerJobByTime(DateTime currentTime, BillingPeriod billingPeriod) {
+
+ DateTime dateTimeToFromBillingPeriod = new DateTime(billingPeriod.getTo()).withZone(DateTimeZone.UTC);
+
+ log.info("Comparing current time[{}, {}] and from scheduler info [{}, {}]", currentTime,
+ currentTime.toDateMidnight(),
+ dateTimeToFromBillingPeriod, dateTimeToFromBillingPeriod.toDateMidnight());
+
+ if (currentTime.toDateMidnight().isAfter(dateTimeToFromBillingPeriod.toDateMidnight())
+ || currentTime.toDateMidnight().isEqual(dateTimeToFromBillingPeriod.toDateMidnight())) {
+ log.info("Should trigger the job by time");
+ return true;
+ }
+
+ log.info("Should not trigger the job by time");
+ return false;
+ }
+
+ private List<BillingData> getBillingData(BillingPeriod billingPeriod) {
+ AzureInvoiceCalculationService azureInvoiceCalculationService
+ = new AzureInvoiceCalculationService(billingConfigurationAzure);
+
+ List<AzureDailyResourceInvoice> dailyInvoices = azureInvoiceCalculationService.generateInvoiceData(
+ DATE_TIME_FORMATTER.print(new DateTime(billingPeriod.getFrom()).withZone(DateTimeZone.UTC)),
+ DATE_TIME_FORMATTER.print(new DateTime(billingPeriod.getTo()).withZone(DateTimeZone.UTC)));
+
+ if (!dailyInvoices.isEmpty()) {
+ return dailyInvoices
+ .stream()
+ .map(this::toBillingData)
+ .collect(Collectors.toList());
+ } else {
+ log.warn("Daily invoices is empty for period {}", billingPeriod);
+ return Collections.emptyList();
+ }
+ }
+
+ private void updateBillingPeriod(BillingPeriod billingPeriod, DateTime currentTime, boolean updates) {
+
+ try {
+ mongoDbBillingClient.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER_HISTORY).insertOne(
+ Document.parse(objectMapper.writeValueAsString(billingPeriod)).append("updates", updates));
+ log.debug("History of billing periods is updated with {}",
+ objectMapper.writeValueAsString(billingPeriod));
+ } catch (JsonProcessingException e) {
+ log.error("Cannot update history of billing periods", e);
+
+ }
+
+ billingPeriod.setFrom(billingPeriod.getTo());
+
+ if (new DateTime(billingPeriod.getFrom()).withZone(DateTimeZone.UTC).toDateMidnight()
+ .isEqual(currentTime.toDateMidnight())) {
+
+ log.info("Setting billing to one day later");
+ billingPeriod.setTo(currentTime.plusDays(1).toDateMidnight().toDate());
+
+ } else {
+ billingPeriod.setTo(currentTime.toDateMidnight().toDate());
+ }
+
+ saveBillingPeriod(billingPeriod);
+ }
+
+ private boolean saveBillingPeriod(BillingPeriod billingPeriod) {
+ log.debug("Saving billing period {}", billingPeriod);
+
+ try {
+ UpdateResult updateResult = mongoDbBillingClient.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER)
+ .updateMany(Filters.eq(MongoKeyWords.MONGO_ID, SCHEDULER_ID),
+ new BasicDBObject("$set",
+ Document.parse(objectMapper.writeValueAsString(billingPeriod))
+ .append(MongoKeyWords.MONGO_ID, SCHEDULER_ID))
+ , new UpdateOptions().upsert(true)
+ );
+
+ log.debug("Billing period save operation result is {}", updateResult);
+ return true;
+ } catch (JsonProcessingException e) {
+ log.error("Cannot save billing period", e);
+ }
+
+ return false;
+ }
+
+ private BillingPeriod getBillingPeriod() {
+ log.debug("Get billing period");
+
+ try {
+ Document document = mongoDbBillingClient.getDatabase().getCollection(MongoKeyWords.AZURE_BILLING_SCHEDULER)
+ .find(Filters.eq(MongoKeyWords.MONGO_ID, SCHEDULER_ID)).first();
+
+ log.debug("Retrieved billing period document {}", document);
+ if (document != null) {
+ return objectMapper.readValue(document.toJson(), BillingPeriod.class);
+ }
+
+ return null;
+
+ } catch (IOException e) {
+ log.error("Cannot save billing period", e);
+ throw new DlabException("Cannot parse string", e);
+ }
+ }
+
+ private BillingData toBillingData(AzureDailyResourceInvoice billingData) {
+ return BillingData.builder()
+ .tag(billingData.getDlabId().toLowerCase())
+ .usageDateFrom(Optional.ofNullable(billingData.getUsageStartDate()).map(LocalDate::parse).orElse(null))
+ .usageDateTo(Optional.ofNullable(billingData.getUsageEndDate()).map(LocalDate::parse).orElse(null))
+ .usageDate(billingData.getDay())
+ .product(billingData.getMeterCategory())
+ .cost(billingData.getCost())
+ .currency(billingData.getCurrencyCode())
+ .build();
+ }
+}
diff --git a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/controller/BillingController.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/controller/BillingController.java
index eb728c9..9018791 100644
--- a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/controller/BillingController.java
+++ b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/controller/BillingController.java
@@ -19,12 +19,11 @@
package com.epam.dlab.billing.azure.controller;
-import com.epam.dlab.billing.azure.dao.BillingDAO;
+import com.epam.dlab.billing.azure.CalculateBillingService;
import com.epam.dlab.dto.billing.BillingData;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -32,22 +31,14 @@
@RestController
public class BillingController {
- private final BillingDAO billingDAO;
+ private final CalculateBillingService billingService;
- public BillingController(BillingDAO billingDAO) {
- this.billingDAO = billingDAO;
+ public BillingController(CalculateBillingService billingService) {
+ this.billingService = billingService;
}
@GetMapping
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam List<String> dlabIds) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dlabIds), HttpStatus.OK);
- }
-
- @GetMapping("/report")
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam("date-start") String dateStart,
- @RequestParam("date-end") String dateEnd,
- @RequestParam("dlab-id") String dlabId,
- @RequestParam("product") List<String> products) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dateStart, dateEnd, dlabId, products), HttpStatus.OK);
+ public ResponseEntity<List<BillingData>> getBilling() {
+ return new ResponseEntity<>(billingService.getBillingData(), HttpStatus.OK);
}
}
diff --git a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/BillingDAO.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/BillingDAO.java
deleted file mode 100644
index 793d7cb..0000000
--- a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/BillingDAO.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 com.epam.dlab.billing.azure.dao;
-
-import com.epam.dlab.dto.billing.BillingData;
-
-import java.util.List;
-
-public interface BillingDAO {
-
- List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products);
-
- List<BillingData> getBillingReport(List<String> dlabIds);
-}
diff --git a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/impl/BillingDAOImpl.java b/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/impl/BillingDAOImpl.java
deleted file mode 100644
index c39385a..0000000
--- a/services/billing-azure/src/main/java/com/epam/dlab/billing/azure/dao/impl/BillingDAOImpl.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 com.epam.dlab.billing.azure.dao.impl;
-
-import com.epam.dlab.billing.azure.dao.BillingDAO;
-import com.epam.dlab.billing.azure.model.AzureDailyResourceInvoice;
-import com.epam.dlab.dto.billing.BillingData;
-import com.epam.dlab.exceptions.DlabException;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.aggregation.Aggregation;
-import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
-import org.springframework.data.mongodb.core.aggregation.GroupOperation;
-import org.springframework.data.mongodb.core.aggregation.MatchOperation;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
-
-@Component
-@Slf4j
-public class BillingDAOImpl implements BillingDAO {
- private final MongoTemplate mongoTemplate;
-
- public BillingDAOImpl(MongoTemplate mongoTemplate) {
- this.mongoTemplate = mongoTemplate;
- }
-
- @Override
- public List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products) {
- try {
- List<AggregationOperation> aggregationOperations = new ArrayList<>();
- aggregationOperations.add(Aggregation.match(Criteria.where("dlabId").regex(dlabId, "i")));
- if (!products.isEmpty()) {
- aggregationOperations.add(Aggregation.match(Criteria.where("meterCategory").in(products)));
- }
- getMatchCriteria(dateStart, Criteria.where("day").gte(dateStart))
- .ifPresent(aggregationOperations::add);
- getMatchCriteria(dateEnd, Criteria.where("day").lte(dateEnd))
- .ifPresent(aggregationOperations::add);
- aggregationOperations.add(getGroupOperation());
-
- Aggregation aggregation = newAggregation(aggregationOperations);
-
- return mongoTemplate.aggregate(aggregation, "billing", AzureDailyResourceInvoice.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- @Override
- public List<BillingData> getBillingReport(List<String> dlabIds) {
- try {
- GroupOperation groupOperation = getGroupOperation();
- MatchOperation matchOperation = Aggregation.match(Criteria.where("dlab_id").in(dlabIds));
- Aggregation aggregation = newAggregation(matchOperation, groupOperation);
-
- return mongoTemplate.aggregate(aggregation, "billing", AzureDailyResourceInvoice.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- private GroupOperation getGroupOperation() {
- return group("meterCategory", "currencyCode", "dlabId")
- .min("usageStartDate").as("usageStartDate")
- .max("usageEndDate").as("usageEndDate")
- .sum("cost").as("cost");
- }
-
- private Optional<MatchOperation> getMatchCriteria(String dateStart, Criteria criteria) {
- return Optional.ofNullable(dateStart)
- .filter(StringUtils::isNotEmpty)
- .map(date -> Aggregation.match(criteria));
- }
-
- private BillingData toBillingData(AzureDailyResourceInvoice billingData) {
- return BillingData.builder()
- .tag(billingData.getDlabId())
- .usageDateFrom(Optional.ofNullable(billingData.getUsageStartDate()).map(LocalDate::parse).orElse(null))
- .usageDateTo(Optional.ofNullable(billingData.getUsageEndDate()).map(LocalDate::parse).orElse(null))
- .product(billingData.getMeterCategory())
- .cost(BigDecimal.valueOf(billingData.getCost()).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue())
- .currency(billingData.getCurrencyCode())
- .build();
- }
-}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/BillingGcpApplication.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/BillingGcpApplication.java
index 0e31323..c454038 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/BillingGcpApplication.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/BillingGcpApplication.java
@@ -23,10 +23,8 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
-import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
-@EnableScheduling
@EnableMongoRepositories
@EnableConfigurationProperties
public class BillingGcpApplication {
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/controller/BillingController.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/controller/BillingController.java
index 2967d2a..ea45d89 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/controller/BillingController.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/controller/BillingController.java
@@ -19,12 +19,11 @@
package com.epam.dlab.billing.gcp.controller;
-import com.epam.dlab.billing.gcp.dao.BillingDAO;
+import com.epam.dlab.billing.gcp.service.BillingService;
import com.epam.dlab.dto.billing.BillingData;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -32,22 +31,14 @@
@RestController
public class BillingController {
- private final BillingDAO billingDAO;
+ private final BillingService billingService;
- public BillingController(BillingDAO billingDAO) {
- this.billingDAO = billingDAO;
+ public BillingController(BillingService billingService) {
+ this.billingService = billingService;
}
@GetMapping
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam List<String> dlabIds) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dlabIds), HttpStatus.OK);
- }
-
- @GetMapping("/report")
- public ResponseEntity<List<BillingData>> getBilling(@RequestParam("date-start") String dateStart,
- @RequestParam("date-end") String dateEnd,
- @RequestParam("dlab-id") String dlabId,
- @RequestParam("product") List<String> products) {
- return new ResponseEntity<>(billingDAO.getBillingReport(dateStart, dateEnd, dlabId, products), HttpStatus.OK);
+ public ResponseEntity<List<BillingData>> getBilling() {
+ return new ResponseEntity<>(billingService.getBillingData(), HttpStatus.OK);
}
}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/BillingDAO.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/BillingDAO.java
index 430ade7..7c791df 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/BillingDAO.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/BillingDAO.java
@@ -19,16 +19,10 @@
package com.epam.dlab.billing.gcp.dao;
-import com.epam.dlab.billing.gcp.model.GcpBillingData;
import com.epam.dlab.dto.billing.BillingData;
import java.util.List;
public interface BillingDAO {
-
- List<GcpBillingData> getBillingData() throws InterruptedException;
-
- List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products);
-
- List<BillingData> getBillingReport(List<String> dlabIds);
+ List<BillingData> getBillingData() throws InterruptedException;
}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/impl/BigQueryBillingDAO.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/impl/BigQueryBillingDAO.java
index 6db993a..061283d 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/impl/BigQueryBillingDAO.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/dao/impl/BigQueryBillingDAO.java
@@ -22,10 +22,8 @@
import com.epam.dlab.billing.gcp.conf.DlabConfiguration;
import com.epam.dlab.billing.gcp.dao.BillingDAO;
import com.epam.dlab.billing.gcp.model.BillingHistory;
-import com.epam.dlab.billing.gcp.model.GcpBillingData;
import com.epam.dlab.billing.gcp.repository.BillingHistoryRepository;
import com.epam.dlab.dto.billing.BillingData;
-import com.epam.dlab.exceptions.DlabException;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.FieldValueList;
import com.google.cloud.bigquery.QueryJobConfiguration;
@@ -33,32 +31,20 @@
import com.google.cloud.bigquery.Table;
import com.google.cloud.bigquery.TableInfo;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.aggregation.Aggregation;
-import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
-import org.springframework.data.mongodb.core.aggregation.GroupOperation;
-import org.springframework.data.mongodb.core.aggregation.MatchOperation;
-import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Component;
-import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
-import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
-
@Component
@Slf4j
public class BigQueryBillingDAO implements BillingDAO {
@@ -92,7 +78,7 @@
}
@Override
- public List<GcpBillingData> getBillingData() {
+ public List<BillingData> getBillingData() {
final Map<String, Long> processedBillingTables = billingHistoryRepo.findAll()
.stream()
.collect(Collectors.toMap(BillingHistory::getTableName, BillingHistory::getLastModified));
@@ -107,63 +93,7 @@
.collect(Collectors.toList());
}
- @Override
- public List<BillingData> getBillingReport(String dateStart, String dateEnd, String dlabId, List<String> products) {
- try {
- List<AggregationOperation> aggregationOperations = new ArrayList<>();
- aggregationOperations.add(Aggregation.match(Criteria.where("dlabId").regex(dlabId, "i")));
- if (!products.isEmpty()) {
- aggregationOperations.add(Aggregation.match(Criteria.where("product").in(products)));
- }
- getMatchCriteria(dateStart, Criteria.where("usage_date").gte(dateStart))
- .ifPresent(aggregationOperations::add);
- getMatchCriteria(dateEnd, Criteria.where("usage_date").lte(dateEnd))
- .ifPresent(aggregationOperations::add);
- aggregationOperations.add(getGroupOperation());
-
- Aggregation aggregation = newAggregation(aggregationOperations);
-
- return mongoTemplate.aggregate(aggregation, "billing", GcpBillingData.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- @Override
- public List<BillingData> getBillingReport(List<String> dlabIds) {
- try {
- GroupOperation groupOperation = getGroupOperation();
- MatchOperation matchOperation = Aggregation.match(Criteria.where("dlabId").in(dlabIds));
- Aggregation aggregation = newAggregation(matchOperation, groupOperation);
-
- return mongoTemplate.aggregate(aggregation, "billing", GcpBillingData.class).getMappedResults()
- .stream()
- .map(this::toBillingData)
- .collect(Collectors.toList());
- } catch (Exception e) {
- log.error("Cannot retrieve billing information ", e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- }
-
- private GroupOperation getGroupOperation() {
- return group("product", "currency", "dlabId")
- .min("from").as("from")
- .max("to").as("to")
- .sum("cost").as("cost");
- }
-
- private Optional<MatchOperation> getMatchCriteria(String dateStart, Criteria criteria) {
- return Optional.ofNullable(dateStart)
- .filter(StringUtils::isNotEmpty)
- .map(date -> Aggregation.match(criteria));
- }
-
- private Stream<? extends GcpBillingData> bigQueryResultSetStream(Table table) {
+ private Stream<? extends BillingData> bigQueryResultSetStream(Table table) {
try {
final String tableName = table.getTableId().getTable();
final String tableId = table.getTableId().getDataset() + "." + tableName;
@@ -172,7 +102,7 @@
.addNamedParameter(SBN_PARAM, QueryParameterValue.string(sbn + "%"))
.addNamedParameter(DATASET_PARAM, QueryParameterValue.string(tableId))
.build();
- final Stream<GcpBillingData> gcpBillingDataStream =
+ final Stream<BillingData> gcpBillingDataStream =
StreamSupport.stream(service.query(queryConfig).getValues().spliterator(), false)
.map(this::toGcpBillingData);
billingHistoryRepo.save(new BillingHistory(tableName, table.getLastModifiedTime()));
@@ -182,15 +112,15 @@
}
}
- private GcpBillingData toGcpBillingData(FieldValueList fields) {
- return GcpBillingData.builder()
+ private BillingData toGcpBillingData(FieldValueList fields) {
+ return BillingData.builder()
.usageDateFrom(toLocalDate(fields, "usage_date_from"))
.usageDateTo(toLocalDate(fields, "usage_date_to"))
.cost(fields.get("cost").getNumericValue().doubleValue())
.product(fields.get("product").getStringValue())
.usageType(fields.get("usageType").getStringValue())
.currency(fields.get("currency").getStringValue())
- .tag(fields.get("value").getStringValue())
+ .tag(fields.get("value").getStringValue().toLowerCase())
.usageDate(toLocalDate(fields, "usage_date_from").format((DateTimeFormatter.ofPattern(DATE_FORMAT))))
.build();
}
@@ -199,16 +129,4 @@
return LocalDate.from(Instant.ofEpochMilli(fieldValues.get(timestampFieldName).getTimestampValue() / 1000)
.atZone(ZoneId.systemDefault()));
}
-
- private BillingData toBillingData(GcpBillingData billingData) {
- return BillingData.builder()
- .usageDateFrom(billingData.getUsageDateFrom())
- .usageDateTo(billingData.getUsageDateTo())
- .product(billingData.getProduct())
- .usageType(billingData.getUsageType())
- .cost(BigDecimal.valueOf(billingData.getCost()).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue())
- .currency(billingData.getCurrency())
- .tag(billingData.getTag())
- .build();
- }
}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/scheduler/BillingScheduler.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/scheduler/BillingScheduler.java
deleted file mode 100644
index 9724d43..0000000
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/scheduler/BillingScheduler.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 com.epam.dlab.billing.gcp.scheduler;
-
-import com.epam.dlab.billing.gcp.service.BillingService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-@Component
-public class BillingScheduler {
-
- private final BillingService billingService;
-
- @Autowired
- public BillingScheduler(BillingService billingService) {
- this.billingService = billingService;
- }
-
-
- @Scheduled(cron = "${dlab.cron}")
- public void getBillingReport() {
- billingService.updateBillingData();
- }
-}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/BillingService.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/BillingService.java
index bb56d6c..7bb3246 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/BillingService.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/BillingService.java
@@ -19,6 +19,10 @@
package com.epam.dlab.billing.gcp.service;
+import com.epam.dlab.dto.billing.BillingData;
+
+import java.util.List;
+
public interface BillingService {
- void updateBillingData();
+ List<BillingData> getBillingData();
}
diff --git a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/impl/BillingServiceImpl.java b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/impl/BillingServiceImpl.java
index c37436b..5661dfb 100644
--- a/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/impl/BillingServiceImpl.java
+++ b/services/billing-gcp/src/main/java/com/epam/dlab/billing/gcp/service/impl/BillingServiceImpl.java
@@ -20,45 +20,33 @@
package com.epam.dlab.billing.gcp.service.impl;
import com.epam.dlab.billing.gcp.dao.BillingDAO;
-import com.epam.dlab.billing.gcp.model.GcpBillingData;
-import com.epam.dlab.billing.gcp.repository.BillingRepository;
import com.epam.dlab.billing.gcp.service.BillingService;
+import com.epam.dlab.dto.billing.BillingData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
@Service
@Slf4j
public class BillingServiceImpl implements BillingService {
- private static final String USAGE_DATE_FORMAT = "yyyy-MM";
private final BillingDAO billingDAO;
- private final BillingRepository billingRepository;
@Autowired
- public BillingServiceImpl(BillingDAO billingDAO, BillingRepository billingRepository) {
+ public BillingServiceImpl(BillingDAO billingDAO) {
this.billingDAO = billingDAO;
- this.billingRepository = billingRepository;
}
@Override
- public void updateBillingData() {
+ public List<BillingData> getBillingData() {
try {
- Map<String, List<GcpBillingData>> billingData = billingDAO.getBillingData()
- .stream()
- .collect(Collectors.groupingBy(bd -> bd.getUsageDate().substring(0, USAGE_DATE_FORMAT.length())));
-
- billingData.forEach((usageDate, billingDataList) -> {
- log.info("Updating billing information for month {}", usageDate);
- billingRepository.deleteByUsageDateRegex("^" + usageDate);
- billingRepository.insert(billingDataList);
- });
+ return billingDAO.getBillingData();
} catch (Exception e) {
log.error("Can not update billing due to: {}", e.getMessage(), e);
+ return Collections.emptyList();
}
}
}
diff --git a/services/billing-gcp/src/test/java/com/epam/dlab/billing/gcp/service/BillingServiceImplTest.java b/services/billing-gcp/src/test/java/com/epam/dlab/billing/gcp/service/BillingServiceImplTest.java
deleted file mode 100644
index 2ff670a..0000000
--- a/services/billing-gcp/src/test/java/com/epam/dlab/billing/gcp/service/BillingServiceImplTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.epam.dlab.billing.gcp.service;
-
-import com.epam.dlab.billing.gcp.dao.BillingDAO;
-import com.epam.dlab.billing.gcp.model.GcpBillingData;
-import com.epam.dlab.billing.gcp.repository.BillingRepository;
-import com.epam.dlab.billing.gcp.service.impl.BillingServiceImpl;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import java.time.LocalDate;
-import java.util.Collections;
-import java.util.List;
-
-import static org.mockito.Mockito.anyCollection;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-
-@RunWith(MockitoJUnitRunner.class)
-public class BillingServiceImplTest {
- @Mock
- private BillingDAO billingDAO;
- @Mock
- private BillingRepository billingRepository;
- @InjectMocks
- private BillingServiceImpl billingService;
-
- @Test
- public void updateBillingData() throws InterruptedException {
- when(billingDAO.getBillingData()).thenReturn(getBillingData());
-
- billingService.updateBillingData();
-
- verify(billingDAO).getBillingData();
- verify(billingRepository).deleteByUsageDateRegex(anyString());
- verify(billingRepository).insert(anyCollection());
-
- verifyNoMoreInteractions(billingDAO);
- }
-
- private List<GcpBillingData> getBillingData() {
- return Collections.singletonList(GcpBillingData.builder()
- .usageDate(LocalDate.MIN.toString())
- .usageDateFrom(LocalDate.MIN)
- .usageDateTo(LocalDate.MAX)
- .product("product")
- .usageType("usageType")
- .cost(1d)
- .currency("USD")
- .tag("exploratoryId")
- .build());
- }
-}
\ No newline at end of file
diff --git a/services/dlab-model/src/main/java/com/epam/dlab/dto/billing/BillingData.java b/services/dlab-model/src/main/java/com/epam/dlab/dto/billing/BillingData.java
index 27db613..c95a02e 100644
--- a/services/dlab-model/src/main/java/com/epam/dlab/dto/billing/BillingData.java
+++ b/services/dlab-model/src/main/java/com/epam/dlab/dto/billing/BillingData.java
@@ -31,6 +31,7 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class BillingData {
private final String tag;
+ private String application;
@JsonProperty("from")
private LocalDate usageDateFrom;
@JsonProperty("to")
@@ -39,4 +40,5 @@
private String usageType;
private Double cost;
private String currency;
+ private final String usageDate;
}
diff --git a/services/dlab-model/src/main/java/com/epam/dlab/dto/computational/UserComputationalResource.java b/services/dlab-model/src/main/java/com/epam/dlab/dto/computational/UserComputationalResource.java
index 56da2f6..9f8c021 100644
--- a/services/dlab-model/src/main/java/com/epam/dlab/dto/computational/UserComputationalResource.java
+++ b/services/dlab-model/src/main/java/com/epam/dlab/dto/computational/UserComputationalResource.java
@@ -59,6 +59,8 @@
private LocalDateTime lastActivity;
@JsonProperty("master_node_shape")
private String masterNodeShape;
+ @JsonProperty("slave_node_shape")
+ private String slaveNodeShape;
@JsonProperty("dataengine_instance_shape")
private String dataengineShape;
@JsonProperty("dataengine_instance_count")
diff --git a/services/dlab-model/src/main/java/com/epam/dlab/dto/exploratory/ExploratoryImageDTO.java b/services/dlab-model/src/main/java/com/epam/dlab/dto/exploratory/ExploratoryImageDTO.java
index 0dad8e4..b41f432 100644
--- a/services/dlab-model/src/main/java/com/epam/dlab/dto/exploratory/ExploratoryImageDTO.java
+++ b/services/dlab-model/src/main/java/com/epam/dlab/dto/exploratory/ExploratoryImageDTO.java
@@ -35,6 +35,8 @@
private Map<String, String> tags;
@JsonProperty("endpoint_name")
private String endpoint;
+ @JsonProperty("conf_shared_image_enabled")
+ private String sharedImageEnabled;
public ExploratoryImageDTO withImageName(String imageName) {
this.imageName = imageName;
@@ -51,6 +53,11 @@
return this;
}
+ public ExploratoryImageDTO withSharedImageEnabled(String sharedImageEnabled) {
+ this.sharedImageEnabled = sharedImageEnabled;
+ return this;
+ }
+
@Override
public MoreObjects.ToStringHelper toStringHelper(Object self) {
return super.toStringHelper(self)
diff --git a/services/dlab-webapp-common/src/main/java/com/epam/dlab/ServiceConfiguration.java b/services/dlab-webapp-common/src/main/java/com/epam/dlab/ServiceConfiguration.java
index 85d4318..3297abd 100644
--- a/services/dlab-webapp-common/src/main/java/com/epam/dlab/ServiceConfiguration.java
+++ b/services/dlab-webapp-common/src/main/java/com/epam/dlab/ServiceConfiguration.java
@@ -54,6 +54,11 @@
@Valid
@NotNull
+ @JsonProperty(ServiceConsts.BILLING_SERVICE_NAME)
+ private RESTServiceFactory billingFactory = new RESTServiceFactory();
+
+ @Valid
+ @NotNull
@JsonProperty(ServiceConsts.SECURITY_SERVICE_NAME)
private RESTServiceFactory securityFactory;
@@ -85,6 +90,10 @@
return provisioningFactory;
}
+ public RESTServiceFactory getBillingFactory() {
+ return billingFactory;
+ }
+
public RESTServiceFactory getSecurityFactory() {
return securityFactory;
}
diff --git a/services/dlab-webapp-common/src/main/java/com/epam/dlab/constants/ServiceConsts.java b/services/dlab-webapp-common/src/main/java/com/epam/dlab/constants/ServiceConsts.java
index d376665..e1bcf23 100644
--- a/services/dlab-webapp-common/src/main/java/com/epam/dlab/constants/ServiceConsts.java
+++ b/services/dlab-webapp-common/src/main/java/com/epam/dlab/constants/ServiceConsts.java
@@ -20,13 +20,14 @@
package com.epam.dlab.constants;
public final class ServiceConsts {
- public static final String MONGO_NAME = "mongo";
- public static final String PROVISIONING_SERVICE_NAME = "provisioningService";
- public static final String MAVEN_SEARCH_API = "mavenSearchService";
- public static final String SECURITY_SERVICE_NAME = "securityService";
- public static final String SELF_SERVICE_NAME = "selfService";
- public static final String PROVISIONING_USER_AGENT = "provisioning-service";
+ public static final String MONGO_NAME = "mongo";
+ public static final String PROVISIONING_SERVICE_NAME = "provisioningService";
+ public static final String BILLING_SERVICE_NAME = "billingService";
+ public static final String MAVEN_SEARCH_API = "mavenSearchService";
+ public static final String SECURITY_SERVICE_NAME = "securityService";
+ public static final String SELF_SERVICE_NAME = "selfService";
+ public static final String PROVISIONING_USER_AGENT = "provisioning-service";
- private ServiceConsts() {
- }
+ private ServiceConsts() {
+ }
}
diff --git a/services/self-service/self-service.yml b/services/self-service/self-service.yml
index 53bd131..df92c25 100644
--- a/services/self-service/self-service.yml
+++ b/services/self-service/self-service.yml
@@ -140,16 +140,19 @@
cron: "*/20 * * ? * * *"
checkQuoteScheduler:
enabled: true
- cron: "0 0 * ? * * *"
+ cron: "0 2/15 * ? * *"
checkUserQuoteScheduler:
enabled: false
cron: "0 0 * ? * * *"
checkProjectQuoteScheduler:
enabled: true
- cron: "0 * * ? * * *"
+ cron: "0 4/15 * ? * *"
checkEndpointStatusScheduler:
enabled: true
- cron: "0 */15 * ? * *"
+ cron: "0 6/15 * ? * *"
+ billingScheduler:
+ enabled: true
+ cron: "0 0/15 * ? * *"
guacamole:
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseBillingDAO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseBillingDAO.java
index d22e400..28a6c64 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseBillingDAO.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseBillingDAO.java
@@ -19,30 +19,62 @@
package com.epam.dlab.backendapi.dao;
+import com.epam.dlab.backendapi.domain.BillingReportLine;
+import com.epam.dlab.backendapi.resources.dto.BillingFilter;
+import com.epam.dlab.dto.billing.BillingResourceType;
import com.google.inject.Inject;
+import com.mongodb.client.model.Aggregates;
+import com.mongodb.client.model.Filters;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.Document;
import org.bson.conversions.Bson;
import java.math.BigDecimal;
+import java.time.ZoneId;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import static com.epam.dlab.backendapi.dao.MongoCollections.BILLING;
+import static com.mongodb.client.model.Accumulators.max;
+import static com.mongodb.client.model.Accumulators.min;
import static com.mongodb.client.model.Accumulators.sum;
import static com.mongodb.client.model.Aggregates.group;
import static com.mongodb.client.model.Aggregates.match;
+import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Filters.gte;
+import static com.mongodb.client.model.Filters.in;
+import static com.mongodb.client.model.Filters.lte;
+import static com.mongodb.client.model.Filters.regex;
import static java.util.Collections.singletonList;
@Slf4j
public class BaseBillingDAO extends BaseDAO implements BillingDAO {
-
- private static final String PROJECT = "project";
private static final int ONE_HUNDRED = 100;
- private static final String TOTAL_FIELD_NAME = "total";
private static final String COST_FIELD = "$cost";
+ private static final String TOTAL_FIELD_NAME = "total";
+ private static final String PROJECT = "project";
+ private static final String APPLICATION = "application";
+ private static final String USAGE_DATE = "usageDate";
+ private static final String USER = "user";
+ private static final String RESOURCE_TYPE = "resource_type";
+ private static final String DLAB_ID = "dlabId";
+ private static final String FROM = "from";
+ private static final String TO = "to";
+ private static final String PRODUCT = "product";
+ private static final String CURRENCY = "currency";
+ private static final String COST = "cost";
+ private static final String RESOURCE_NAME = "resource_name";
+ private static final String ENDPOINT = "endpoint";
+ private static final String SHAPE = "shape";
+ private static final String EXPLORATORY = "exploratoryName";
@Inject
protected SettingsDAO settings;
@@ -102,10 +134,44 @@
}
@Override
+ public List<BillingReportLine> findBillingData(String project, String endpoint, List<String> resourceNames) {
+ return find(BILLING, and(eq(PROJECT, project), eq(ENDPOINT, endpoint), in(RESOURCE_NAME, resourceNames)), BillingReportLine.class);
+ }
+
+ @Override
public int getBillingProjectQuoteUsed(String project) {
return toPercentage(() -> projectDAO.getAllowedBudget(project), getProjectCost(project));
}
+ public List<BillingReportLine> aggregateBillingData(BillingFilter filter) {
+ List<Bson> pipeline = new ArrayList<>();
+ List<Bson> matchCriteria = matchCriteria(filter);
+ if (!matchCriteria.isEmpty()) {
+ pipeline.add(Aggregates.match(Filters.and(matchCriteria)));
+ }
+ pipeline.add(groupCriteria());
+ return StreamSupport.stream(getCollection(BILLING).aggregate(pipeline).spliterator(), false)
+ .map(this::toBillingReport)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void deleteByUsageDate(String application, String usageDate) {
+ deleteMany(BILLING, and(eq(APPLICATION, application), eq(USAGE_DATE, usageDate)));
+ }
+
+ @Override
+ public void deleteByUsageDateRegex(String application, String usageDate) {
+ deleteMany(BILLING, and(eq(APPLICATION, application), regex(USAGE_DATE, "^" + usageDate)));
+ }
+
+ @Override
+ public void save(List<BillingReportLine> billingData) {
+ if (CollectionUtils.isNotEmpty(billingData)) {
+ insertMany(BILLING, new ArrayList<>(billingData));
+ }
+ }
+
private Integer toPercentage(Supplier<Optional<Integer>> allowedBudget, Double totalCost) {
return allowedBudget.get()
.map(userBudget -> (totalCost * ONE_HUNDRED) / userBudget)
@@ -118,4 +184,60 @@
.map(d -> d.getDouble(TOTAL_FIELD_NAME))
.orElse(BigDecimal.ZERO.doubleValue());
}
+
+ private Bson groupCriteria() {
+ return group(getGroupingFields(USER, DLAB_ID, RESOURCE_TYPE, RESOURCE_NAME, PROJECT, PRODUCT, CURRENCY, SHAPE, EXPLORATORY),
+ sum(COST, "$" + COST),
+ min(FROM, "$" + FROM),
+ max(TO, "$" + TO));
+ }
+
+ private List<Bson> matchCriteria(BillingFilter filter) {
+ List<Bson> searchCriteria = new ArrayList<>();
+
+ if (CollectionUtils.isNotEmpty(filter.getUsers())) {
+ searchCriteria.add(in(USER, filter.getUsers()));
+ }
+ if (CollectionUtils.isNotEmpty(filter.getResourceTypes())) {
+ searchCriteria.add(in(RESOURCE_TYPE, filter.getResourceTypes()));
+ }
+ if (StringUtils.isNotEmpty(filter.getDlabId())) {
+ searchCriteria.add(regex(DLAB_ID, filter.getDlabId(), "i"));
+ }
+ if (StringUtils.isNotEmpty(filter.getDateStart())) {
+ searchCriteria.add(gte(USAGE_DATE, filter.getDateStart()));
+ }
+ if (StringUtils.isNotEmpty(filter.getDateEnd())) {
+ searchCriteria.add(lte(USAGE_DATE, filter.getDateEnd()));
+ }
+ if (CollectionUtils.isNotEmpty(filter.getProjects())) {
+ searchCriteria.add(in(PROJECT, filter.getProjects()));
+ }
+ if (CollectionUtils.isNotEmpty(filter.getProducts())) {
+ searchCriteria.add(in(PRODUCT, filter.getProducts()));
+ }
+ if (CollectionUtils.isNotEmpty(filter.getShapes())) {
+ searchCriteria.add(regex(SHAPE, "(" + String.join("|", filter.getShapes()) + ")"));
+ }
+
+ return searchCriteria;
+ }
+
+ private BillingReportLine toBillingReport(Document d) {
+ Document id = (Document) d.get("_id");
+ return BillingReportLine.builder()
+ .dlabId(id.getString(DLAB_ID))
+ .project(id.getString(PROJECT))
+ .resourceName(id.getString(RESOURCE_NAME))
+ .exploratoryName(id.getString(EXPLORATORY))
+ .shape(id.getString(SHAPE))
+ .user(id.getString(USER))
+ .product(id.getString(PRODUCT))
+ .resourceType(Optional.ofNullable(id.getString(RESOURCE_TYPE)).map(BillingResourceType::valueOf).orElse(null))
+ .usageDateFrom(d.getDate(FROM).toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
+ .usageDateTo(d.getDate(TO).toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
+ .cost(BigDecimal.valueOf(d.getDouble(COST)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue())
+ .currency(id.getString(CURRENCY))
+ .build();
+ }
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseDAO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseDAO.java
index 034011a..c2ff69b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseDAO.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BaseDAO.java
@@ -31,7 +31,11 @@
import com.google.inject.Inject;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoException;
-import com.mongodb.client.*;
+import com.mongodb.client.AggregateIterable;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
@@ -41,13 +45,21 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import static com.mongodb.client.model.Filters.*;
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.exists;
+import static com.mongodb.client.model.Filters.ne;
/**
* Implements the base API for Mongo database.
@@ -158,6 +170,29 @@
}
/**
+ * Serializes objects and inserts into the collection.
+ *
+ * @param collection collection name.
+ * @param object for inserting to collection.
+ */
+ protected void insertMany(String collection, List<Object> object) {
+ try {
+ mongoService.getCollection(collection)
+ .insertMany(convertToBson(object)
+ .stream()
+ .peek(o -> {
+ o.append(ID, generateUUID());
+ o.append(TIMESTAMP, new Date());
+ })
+ .collect(Collectors.toList())
+ );
+ } catch (MongoException e) {
+ LOGGER.warn("Insert to Mongo DB fails: {}", e.getLocalizedMessage(), e);
+ throw new DlabException("Insert to Mongo DB fails: " + e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
* Updates single document in the collection by condition.
*
* @param collection collection name.
@@ -230,6 +265,22 @@
}
/**
+ * Removes many documents in the collection by condition.
+ *
+ * @param collection collection name.
+ * @param condition condition for search documents in collection.
+ */
+ protected DeleteResult deleteMany(String collection, Bson condition) {
+ try {
+ return mongoService.getCollection(collection)
+ .deleteMany(condition);
+ } catch (MongoException e) {
+ LOGGER.warn("Removing document from Mongo DB fails: {}", e.getLocalizedMessage(), e);
+ throw new DlabException("Removing document from Mongo DB fails: " + e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
* Finds and returns all documents from the collection.
*
* @param collection collection name.
@@ -362,6 +413,13 @@
}
}
+ List<Document> convertToBson(List<Object> objects) {
+ return objects
+ .stream()
+ .map(this::convertToBson)
+ .collect(Collectors.toList());
+ }
+
/**
* Finds and returns one object as given class from the collection by condition.
*
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BillingDAO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BillingDAO.java
index d50c62f..67630cd 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BillingDAO.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/BillingDAO.java
@@ -18,6 +18,11 @@
*/
package com.epam.dlab.backendapi.dao;
+import com.epam.dlab.backendapi.domain.BillingReportLine;
+import com.epam.dlab.backendapi.resources.dto.BillingFilter;
+
+import java.util.List;
+
public interface BillingDAO {
Double getTotalCost();
@@ -36,4 +41,14 @@
boolean isUserQuoteReached(String user);
boolean isProjectQuoteReached(String project);
+
+ List<BillingReportLine> findBillingData(String project, String endpoint, List<String> resourceNames);
+
+ List<BillingReportLine> aggregateBillingData(BillingFilter filter);
+
+ void deleteByUsageDate(String application, String usageDate);
+
+ void deleteByUsageDateRegex(String application, String usageDate);
+
+ void save(List<BillingReportLine> billingData);
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDao.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDao.java
index c9b5585..48abb54 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDao.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDao.java
@@ -47,5 +47,5 @@
boolean removeGroup(String groupId);
- List<UserGroupDto> aggregateRolesByGroup(boolean isAdmin);
+ List<UserGroupDto> aggregateRolesByGroup();
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
index c256791..5bc845a 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/UserRoleDaoImpl.java
@@ -21,12 +21,11 @@
import com.epam.dlab.backendapi.resources.dto.UserGroupDto;
import com.epam.dlab.backendapi.resources.dto.UserRoleDto;
import com.epam.dlab.cloud.CloudProvider;
+import com.epam.dlab.exceptions.DlabException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Singleton;
-import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.BsonField;
-import com.mongodb.client.model.Filters;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.bson.conversions.Bson;
@@ -35,9 +34,12 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static com.epam.dlab.backendapi.dao.MongoCollections.USER_GROUPS;
import static com.mongodb.client.model.Aggregates.group;
@@ -67,7 +69,6 @@
private static final String EXPLORATORIES_FIELD = "exploratories";
private static final String COMPUTATIONALS_FIELD = "computationals";
private static final String GROUP_INFO = "groupInfo";
- private static final String ADMIN = "admin";
@Override
@@ -95,11 +96,20 @@
@Override
public void updateMissingRoles(CloudProvider cloudProvider) {
- getUserRoleFromFile(cloudProvider).stream()
- .filter(u -> findAll().stream()
+ getUserRoleFromFile(cloudProvider)
+ .stream()
+ .peek(u -> u.setGroups(Collections.emptySet()))
+ .filter(u -> findAll()
+ .stream()
.map(UserRoleDto::getId)
.noneMatch(id -> id.equals(u.getId())))
.forEach(this::insert);
+
+ addGroupToRole(aggregateRolesByGroup()
+ .stream()
+ .map(UserGroupDto::getGroup)
+ .collect(Collectors.toSet()),
+ getDefaultShapes(cloudProvider));
}
@Override
@@ -142,19 +152,15 @@
}
@Override
- public List<UserGroupDto> aggregateRolesByGroup(boolean isAdmin) {
+ public List<UserGroupDto> aggregateRolesByGroup() {
final Document role = roleDocument();
final Bson groupBy = group(GROUPS, new BsonField(ROLES, new Document(ADD_TO_SET, role)));
final Bson lookup = lookup(USER_GROUPS, ID, ID, GROUP_INFO);
- final List<Bson> pipeline = new ArrayList<>();
- if (!isAdmin) {
- pipeline.add(Aggregates.match(Filters.not(eq(ID, ADMIN))));
- }
- pipeline.addAll(Arrays.asList(unwind(GROUPS), groupBy, lookup,
+ final List<Bson> pipeline = Arrays.asList(unwind(GROUPS), groupBy, lookup,
project(new Document(GROUP, "$" + ID).append(GROUP_INFO, elementAt(GROUP_INFO, 0))
.append(ROLES, "$" + ROLES)),
project(new Document(GROUP, "$" + ID).append(USERS_FIELD, "$" + GROUP_INFO + "." + USERS_FIELD)
- .append(ROLES, "$" + ROLES))));
+ .append(ROLES, "$" + ROLES)));
return stream(aggregate(MongoCollections.ROLES, pipeline))
.map(d -> convertFromDocument(d, UserGroupDto.class))
@@ -170,6 +176,21 @@
}
}
+ private Set<String> getDefaultShapes(CloudProvider cloudProvider) {
+ if (cloudProvider == CloudProvider.AWS) {
+ return Stream.of("nbShapes_t2.medium_fetching", "compShapes_c4.xlarge_fetching")
+ .collect(Collectors.toSet());
+ } else if (cloudProvider == CloudProvider.GCP) {
+ return Stream.of("compShapes_n1-standard-2_fetching", "nbShapes_n1-standard-2_fetching")
+ .collect(Collectors.toSet());
+ } else if (cloudProvider == CloudProvider.AZURE) {
+ return Stream.of("nbShapes_Standard_E4s_v3_fetching", "compShapes_Standard_E4s_v3_fetching")
+ .collect(Collectors.toSet());
+ } else {
+ throw new DlabException("Unsupported cloud provider " + cloudProvider);
+ }
+ }
+
private Document roleDocument() {
return new Document().append(ID, "$" + ID)
.append(DESCRIPTION, "$" + DESCRIPTION)
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReport.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReport.java
index 8722e73..2bb2062 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReport.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReport.java
@@ -30,6 +30,7 @@
@Builder
public class BillingReport {
private String sbn;
+ private String name;
@JsonProperty("report_lines")
private List<BillingReportLine> reportLines;
@JsonProperty("from")
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReportLine.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReportLine.java
index ed97a44..a9cdd12 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReportLine.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/BillingReportLine.java
@@ -21,6 +21,7 @@
import com.epam.dlab.dto.UserInstanceStatus;
import com.epam.dlab.dto.billing.BillingResourceType;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@@ -29,16 +30,20 @@
@Data
@Builder
+@JsonIgnoreProperties(ignoreUnknown = true)
public class BillingReportLine {
private String dlabId;
+ private String application;
@JsonProperty("resource_name")
private String resourceName;
private String project;
+ private String endpoint;
private String user;
@JsonProperty("from")
private LocalDate usageDateFrom;
@JsonProperty("to")
private LocalDate usageDateTo;
+ private String usageDate;
private String product;
private String usageType;
private Double cost;
@@ -47,4 +52,5 @@
private BillingResourceType resourceType;
private UserInstanceStatus status;
private String shape;
+ private String exploratoryName;
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
index 3c00111..9275319 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
@@ -132,6 +132,9 @@
bind(RESTService.class).annotatedWith(Names.named(ServiceConsts.PROVISIONING_SERVICE_NAME))
.toInstance(configuration.getProvisioningFactory()
.build(environment, ServiceConsts.PROVISIONING_SERVICE_NAME));
+ bind(RESTService.class).annotatedWith(Names.named(ServiceConsts.BILLING_SERVICE_NAME))
+ .toInstance(configuration.getBillingFactory()
+ .build(environment, ServiceConsts.BILLING_SERVICE_NAME));
bind(ImageExploratoryService.class).to(ImageExploratoryServiceImpl.class);
bind(ImageExploratoryDao.class).to(ImageExploratoryDaoImpl.class);
bind(BackupService.class).to(BackupServiceImpl.class);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
index ce7ac26..d20adbf 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
@@ -123,6 +123,9 @@
bind(RESTService.class).annotatedWith(Names.named(ServiceConsts.PROVISIONING_SERVICE_NAME))
.toInstance(configuration.getProvisioningFactory().build(environment, ServiceConsts
.PROVISIONING_SERVICE_NAME));
+ bind(RESTService.class).annotatedWith(Names.named(ServiceConsts.BILLING_SERVICE_NAME))
+ .toInstance(configuration.getBillingFactory()
+ .build(environment, ServiceConsts.BILLING_SERVICE_NAME));
bind(ImageExploratoryService.class).to(ImageExploratoryServiceImpl.class);
bind(ImageExploratoryDao.class).to(ImageExploratoryDaoImpl.class);
bind(BackupService.class).to(BackupServiceImpl.class);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/UserRoleResource.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/UserRoleResource.java
index b74ef1c..52ad739 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/UserRoleResource.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/UserRoleResource.java
@@ -51,7 +51,7 @@
@GET
public Response getRoles(@Auth UserInfo userInfo) {
log.debug("Getting all roles for admin {}...", userInfo.getName());
- return Response.ok(userRoleService.getUserRoles(userInfo)).build();
+ return Response.ok(userRoleService.getUserRoles()).build();
}
@POST
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ImageInfoRecord.java
index d430701..ed722ee 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ImageInfoRecord.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ImageInfoRecord.java
@@ -30,6 +30,7 @@
private final String description;
private final String project;
private final String endpoint;
+ private final String user;
private final String application;
private final String fullName;
private final ImageStatus status;
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ProjectInfrastructureInfo.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ProjectInfrastructureInfo.java
index e1f09e9..b9dfd89 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ProjectInfrastructureInfo.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/dto/ProjectInfrastructureInfo.java
@@ -19,8 +19,8 @@
package com.epam.dlab.backendapi.resources.dto;
+import com.epam.dlab.backendapi.domain.BillingReport;
import com.epam.dlab.backendapi.domain.EndpointDTO;
-import com.epam.dlab.dto.billing.BillingData;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.ToString;
@@ -41,7 +41,7 @@
@JsonProperty
private Iterable<Document> exploratory;
@JsonProperty
- private List<BillingData> exploratoryBilling;
+ private List<BillingReport> exploratoryBilling;
@JsonProperty
private List<EndpointDTO> endpoints;
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/schedulers/billing/BillingScheduler.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/schedulers/billing/BillingScheduler.java
new file mode 100644
index 0000000..45563a2
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/schedulers/billing/BillingScheduler.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.epam.dlab.backendapi.schedulers.billing;
+
+import com.epam.dlab.backendapi.schedulers.internal.Scheduled;
+import com.epam.dlab.backendapi.service.BillingService;
+import com.epam.dlab.backendapi.service.SecurityService;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+
+@Scheduled("billingScheduler")
+@Slf4j
+public class BillingScheduler implements Job {
+
+ private final BillingService billingService;
+ private final SecurityService securityService;
+
+ @Inject
+ public BillingScheduler(BillingService billingService, SecurityService securityService) {
+ this.billingService = billingService;
+ this.securityService = securityService;
+ }
+
+ @Override
+ public void execute(JobExecutionContext jobExecutionContext) {
+ log.info("Trying to update billing");
+ try {
+ billingService.updateRemoteBillingData(securityService.getServiceAccountInfo("admin"));
+ } catch (Exception e) {
+ log.error("Something went wrong {}", e.getMessage());
+ }
+ }
+}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/BillingService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/BillingService.java
index 40a5c14..b76b141 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/BillingService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/BillingService.java
@@ -21,10 +21,7 @@
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.domain.BillingReport;
-import com.epam.dlab.backendapi.domain.BillingReportLine;
import com.epam.dlab.backendapi.resources.dto.BillingFilter;
-import com.epam.dlab.dto.UserInstanceDTO;
-import com.epam.dlab.dto.billing.BillingData;
import java.util.List;
@@ -33,7 +30,7 @@
String downloadReport(UserInfo userInfo, BillingFilter filter);
- List<BillingReportLine> getBillingReportLines(UserInfo userInfo, BillingFilter filter);
+ BillingReport getExploratoryBillingData(String project, String endpoint, String exploratoryName, List<String> compNames);
- List<BillingData> getExploratoryRemoteBillingData(UserInfo user, String endpoint, List<UserInstanceDTO> userInstanceDTOS);
+ void updateRemoteBillingData(UserInfo userInfo);
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ExploratoryService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ExploratoryService.java
index 5a43fa6..807df17 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ExploratoryService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ExploratoryService.java
@@ -48,6 +48,8 @@
Optional<UserInstanceDTO> getUserInstance(String user, String project, String exploratoryName);
+ Optional<UserInstanceDTO> getUserInstance(String user, String project, String exploratoryName, boolean includeCompResources);
+
List<UserInstanceDTO> findAll();
List<UserInstanceDTO> findAll(Set<ProjectDTO> projects);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleService.java
index a010684..0b22b1d 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleService.java
@@ -18,14 +18,13 @@
*/
package com.epam.dlab.backendapi.service;
-import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.resources.dto.UserRoleDto;
import java.util.List;
public interface UserRoleService {
- List<UserRoleDto> getUserRoles(UserInfo userInfo);
+ List<UserRoleDto> getUserRoles();
void createRole(UserRoleDto dto);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleServiceImpl.java
index 6940533..92e0afb 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/UserRoleServiceImpl.java
@@ -18,11 +18,8 @@
*/
package com.epam.dlab.backendapi.service;
-import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.dao.UserRoleDao;
import com.epam.dlab.backendapi.resources.dto.UserRoleDto;
-import com.epam.dlab.backendapi.roles.UserRoles;
-import com.epam.dlab.exceptions.DlabException;
import com.epam.dlab.exceptions.ResourceNotFoundException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -30,29 +27,17 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
@Singleton
public class UserRoleServiceImpl implements UserRoleService {
private static final String ROLE_NOT_FOUND_MSG = "Any of role : %s were not found";
- private static final String ADMIN = "admin";
@Inject
private UserRoleDao userRoleDao;
@Override
- public List<UserRoleDto> getUserRoles(UserInfo user) {
- List<UserRoleDto> all = userRoleDao.findAll();
- if (UserRoles.isAdmin(user)) {
- return all;
- } else if (UserRoles.isProjectAdmin(user)) {
- return all
- .stream()
- .filter(role -> !role.getId().equalsIgnoreCase(ADMIN))
- .collect(Collectors.toList());
- } else {
- throw new DlabException(String.format("User %s doesn't have appropriate permission", user));
- }
+ public List<UserRoleDto> getUserRoles() {
+ return userRoleDao.findAll();
}
@Override
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/BillingServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/BillingServiceImpl.java
index 176f22b..8eae49b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/BillingServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/BillingServiceImpl.java
@@ -21,6 +21,7 @@
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.conf.SelfServiceApplicationConfiguration;
+import com.epam.dlab.backendapi.dao.BillingDAO;
import com.epam.dlab.backendapi.dao.ImageExploratoryDao;
import com.epam.dlab.backendapi.domain.BillingReport;
import com.epam.dlab.backendapi.domain.BillingReportLine;
@@ -35,9 +36,11 @@
import com.epam.dlab.backendapi.service.ExploratoryService;
import com.epam.dlab.backendapi.service.ProjectService;
import com.epam.dlab.backendapi.util.BillingUtils;
+import com.epam.dlab.cloud.CloudProvider;
import com.epam.dlab.constants.ServiceConsts;
-import com.epam.dlab.dto.UserInstanceDTO;
+import com.epam.dlab.dto.UserInstanceStatus;
import com.epam.dlab.dto.billing.BillingData;
+import com.epam.dlab.dto.billing.BillingResourceType;
import com.epam.dlab.exceptions.DlabException;
import com.epam.dlab.rest.client.RESTService;
import com.google.common.collect.Lists;
@@ -61,19 +64,13 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public class BillingServiceImpl implements BillingService {
private static final String BILLING_PATH = "/api/billing";
- private static final String BILLING_REPORT_PATH = "/api/billing/report";
+ private static final String USAGE_DATE_FORMAT = "yyyy-MM";
private final ProjectService projectService;
private final EndpointService endpointService;
@@ -81,34 +78,43 @@
private final SelfServiceApplicationConfiguration configuration;
private final RESTService provisioningService;
private final ImageExploratoryDao imageExploratoryDao;
+ private final BillingDAO billingDAO;
private final String sbn;
@Inject
public BillingServiceImpl(ProjectService projectService, EndpointService endpointService,
ExploratoryService exploratoryService, SelfServiceApplicationConfiguration configuration,
- @Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService, ImageExploratoryDao imageExploratoryDao) {
+ @Named(ServiceConsts.BILLING_SERVICE_NAME) RESTService provisioningService, ImageExploratoryDao imageExploratoryDao,
+ BillingDAO billingDAO) {
this.projectService = projectService;
this.endpointService = endpointService;
this.exploratoryService = exploratoryService;
this.configuration = configuration;
this.provisioningService = provisioningService;
this.imageExploratoryDao = imageExploratoryDao;
+ this.billingDAO = billingDAO;
sbn = configuration.getServiceBaseName();
}
@Override
public BillingReport getBillingReport(UserInfo user, BillingFilter filter) {
- List<BillingReportLine> billingReportLines = getBillingReportLines(user, filter);
- LocalDate min = billingReportLines.stream().min(Comparator.comparing(BillingReportLine::getUsageDateFrom)).map(BillingReportLine::getUsageDateFrom).orElse(null);
- LocalDate max = billingReportLines.stream().max(Comparator.comparing(BillingReportLine::getUsageDateTo)).map(BillingReportLine::getUsageDateTo).orElse(null);
- double sum = billingReportLines.stream().mapToDouble(BillingReportLine::getCost).sum();
- String currency = billingReportLines.stream().map(BillingReportLine::getCurrency).distinct().count() == 1 ? billingReportLines.get(0).getCurrency() : null;
+ setUserFilter(user, filter);
+ List<BillingReportLine> billingReportLines = billingDAO.aggregateBillingData(filter)
+ .stream()
+ .peek(this::appendStatuses)
+ .filter(bd -> CollectionUtils.isEmpty(filter.getStatuses()) || filter.getStatuses().contains(bd.getStatus()))
+ .collect(Collectors.toList());
+ final LocalDate min = billingReportLines.stream().min(Comparator.comparing(BillingReportLine::getUsageDateFrom)).map(BillingReportLine::getUsageDateFrom).orElse(null);
+ final LocalDate max = billingReportLines.stream().max(Comparator.comparing(BillingReportLine::getUsageDateTo)).map(BillingReportLine::getUsageDateTo).orElse(null);
+ final double sum = billingReportLines.stream().mapToDouble(BillingReportLine::getCost).sum();
+ final String currency = billingReportLines.stream().map(BillingReportLine::getCurrency).distinct().count() == 1 ? billingReportLines.get(0).getCurrency() : null;
return BillingReport.builder()
+ .name("Billing report")
.sbn(sbn)
.reportLines(billingReportLines)
.usageDateFrom(min)
.usageDateTo(max)
- .totalCost(new BigDecimal(sum).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue())
+ .totalCost(new BigDecimal(sum).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue())
.currency(currency)
.isFull(isFullReport(user))
.build();
@@ -116,11 +122,12 @@
@Override
public String downloadReport(UserInfo user, BillingFilter filter) {
+ boolean isFull = isFullReport(user);
BillingReport report = getBillingReport(user, filter);
StringBuilder builder = new StringBuilder(BillingUtils.getFirstLine(report.getSbn(), report.getUsageDateFrom(), report.getUsageDateTo()));
- builder.append(BillingUtils.getHeader());
+ builder.append(BillingUtils.getHeader(isFull));
try {
- report.getReportLines().forEach(r -> builder.append(BillingUtils.printLine(r)));
+ report.getReportLines().forEach(r -> builder.append(BillingUtils.printLine(r, isFull)));
builder.append(BillingUtils.getTotal(report.getTotalCost(), report.getCurrency()));
return builder.toString();
} catch (Exception e) {
@@ -129,54 +136,67 @@
}
}
- @Override
- public List<BillingReportLine> getBillingReportLines(UserInfo user, BillingFilter filter) {
- setUserFilter(user, filter);
- Set<ProjectDTO> projects = new HashSet<>(projectService.getProjects(user));
- projects.addAll(projectService.getUserProjects(user, false));
-
- final Map<String, BillingReportLine> billableResources = getBillableResources(user, projects);
-
- List<BillingReportLine> billingReport = getRemoteBillingData(user, filter)
+ public BillingReport getExploratoryBillingData(String project, String endpoint, String exploratoryName, List<String> compNames) {
+ List<String> resourceNames = new ArrayList<>(compNames);
+ resourceNames.add(exploratoryName);
+ List<BillingReportLine> billingData = billingDAO.findBillingData(project, endpoint, resourceNames)
.stream()
- .filter(bd -> billableResources.containsKey(bd.getTag()))
- .map(bd -> toBillingData(bd, billableResources.get(bd.getTag())))
- .filter(getBillingReportFilter(filter))
+ .peek(bd -> bd.setCost(BigDecimal.valueOf(bd.getCost()).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue()))
.collect(Collectors.toList());
- log.debug("Billing report: {}", billingReport);
-
- return billingReport;
+ final double sum = billingData.stream().mapToDouble(BillingReportLine::getCost).sum();
+ final String currency = billingData.stream().map(BillingReportLine::getCurrency).distinct().count() == 1 ? billingData.get(0).getCurrency() : null;
+ return BillingReport.builder()
+ .name(exploratoryName)
+ .reportLines(billingData)
+ .totalCost(new BigDecimal(sum).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue())
+ .currency(currency)
+ .build();
}
- private Map<String, BillingReportLine> getBillableResources(UserInfo user, Set<ProjectDTO> projects) {
- Stream<BillingReportLine> billableAdminResources = Stream.empty();
+ public void updateRemoteBillingData(UserInfo userInfo) {
+ List<EndpointDTO> endpoints = endpointService.getEndpoints();
+ if (CollectionUtils.isEmpty(endpoints)) {
+ log.error("Cannot update billing info. There are no endpoints");
+ throw new DlabException("Cannot update billing info. There are no endpoints");
+ }
+
+ Map<EndpointDTO, List<BillingData>> billingDataMap = endpoints
+ .stream()
+ .collect(Collectors.toMap(e -> e, e -> getBillingData(userInfo, e)));
+
+ billingDataMap.forEach((endpointDTO, billingData) -> {
+ log.info("Updating billing information for endpoint {}. Billing data {}", endpointDTO.getName(), billingData);
+ try {
+ updateBillingData(endpointDTO, billingData);
+ } catch (Exception e) {
+ log.error("Something went wrong while trying to update billing for {}. {}", endpointDTO.getName(), e.getMessage());
+ }
+ });
+ }
+
+ private Map<String, BillingReportLine> getBillableResources() {
+ Set<ProjectDTO> projects = new HashSet<>(projectService.getProjects());
+ final Stream<BillingReportLine> ssnBillingDataStream = BillingUtils.ssnBillingDataStream(sbn);
+ final Stream<BillingReportLine> billableEdges = projects
+ .stream()
+ .collect(Collectors.toMap(ProjectDTO::getName, ProjectDTO::getEndpoints))
+ .entrySet()
+ .stream()
+ .flatMap(e -> projectEdges(sbn, e.getKey(), e.getValue()));
+ final Stream<BillingReportLine> billableSharedEndpoints = endpointService.getEndpoints()
+ .stream()
+ .flatMap(endpoint -> BillingUtils.sharedEndpointBillingDataStream(endpoint.getName(), sbn));
final Stream<BillingReportLine> billableUserInstances = exploratoryService.findAll(projects)
.stream()
.filter(userInstance -> Objects.nonNull(userInstance.getExploratoryId()))
- .flatMap(ui -> BillingUtils.exploratoryBillingDataStream(ui, configuration.getMaxSparkInstanceCount(), sbn));
- final Stream<BillingReportLine> billingReportLineStream = projects
+ .flatMap(ui -> BillingUtils.exploratoryBillingDataStream(ui, configuration.getMaxSparkInstanceCount()));
+ final Stream<BillingReportLine> customImages = projects
.stream()
.map(p -> imageExploratoryDao.getImagesForProject(p.getName()))
.flatMap(Collection::stream)
.flatMap(i -> BillingUtils.customImageBillingDataStream(i, sbn));
- if (UserRoles.isAdmin(user)) {
- final Stream<BillingReportLine> billableEdges = projects
- .stream()
- .collect(Collectors.toMap(ProjectDTO::getName, ProjectDTO::getEndpoints))
- .entrySet()
- .stream()
- .flatMap(e -> projectEdges(sbn, e.getKey(), e.getValue()));
- final Stream<BillingReportLine> ssnBillingDataStream = BillingUtils.ssnBillingDataStream(sbn);
- final Stream<BillingReportLine> billableSharedEndpoints = endpointService.getEndpoints()
- .stream()
- .flatMap(endpoint -> BillingUtils.sharedEndpointBillingDataStream(endpoint.getName(), sbn));
-
- billableAdminResources = Stream.of(billableEdges, ssnBillingDataStream, billableSharedEndpoints)
- .flatMap(s -> s);
- }
-
- final Map<String, BillingReportLine> billableResources = Stream.of(billableUserInstances, billingReportLineStream, billableAdminResources)
+ final Map<String, BillingReportLine> billableResources = Stream.of(ssnBillingDataStream, billableEdges, billableSharedEndpoints, billableUserInstances, customImages)
.flatMap(s -> s)
.collect(Collectors.toMap(BillingReportLine::getDlabId, b -> b));
log.debug("Billable resources are: {}", billableResources);
@@ -187,61 +207,62 @@
private Stream<BillingReportLine> projectEdges(String serviceBaseName, String projectName, List<ProjectEndpointDTO> endpoints) {
return endpoints
.stream()
- .flatMap(endpoint -> BillingUtils.edgeBillingDataStream(projectName, serviceBaseName, endpoint.getName(),
- endpoint.getStatus().toString()));
+ .flatMap(endpoint -> BillingUtils.edgeBillingDataStream(projectName, serviceBaseName, endpoint.getName()));
}
- public List<BillingData> getExploratoryRemoteBillingData(UserInfo user, String endpoint, List<UserInstanceDTO> userInstanceDTOS) {
- List<String> dlabIds = null;
- try {
- dlabIds = userInstanceDTOS
- .stream()
- .map(instance -> Stream.concat(BillingUtils.getExploratoryIds(instance.getExploratoryId()).stream(), instance.getResources()
- .stream()
- .map(cr -> BillingUtils.getComputationalIds(cr.getComputationalId()))
- .flatMap(Collection::stream)
- ))
- .flatMap(a -> a)
- .collect(Collectors.toList());
+ private void updateBillingData(EndpointDTO endpointDTO, List<BillingData> billingData) {
+ final String endpointName = endpointDTO.getName();
+ final CloudProvider cloudProvider = endpointDTO.getCloudProvider();
+ final Map<String, BillingReportLine> billableResources = getBillableResources();
+ final Stream<BillingReportLine> billingReportLineStream = billingData
+ .stream()
+ .peek(bd -> bd.setApplication(endpointName))
+ .map(bd -> toBillingReport(bd, getOrDefault(billableResources, bd.getTag())));
- EndpointDTO endpointDTO = endpointService.get(endpoint);
- return provisioningService.get(getBillingUrl(endpointDTO.getUrl(), BILLING_PATH), user.getAccessToken(),
+ if (cloudProvider == CloudProvider.GCP) {
+ final Map<String, List<BillingReportLine>> gcpBillingData = billingReportLineStream
+ .collect(Collectors.groupingBy(bd -> bd.getUsageDate().substring(0, USAGE_DATE_FORMAT.length())));
+ updateGcpBillingData(endpointName, gcpBillingData);
+ } else if (cloudProvider == CloudProvider.AWS) {
+ final Map<String, List<BillingReportLine>> awsBillingData = billingReportLineStream
+ .collect(Collectors.groupingBy(BillingReportLine::getUsageDate));
+ updateAwsBillingData(endpointName, awsBillingData);
+ } else if (cloudProvider == CloudProvider.AZURE) {
+ final List<BillingReportLine> billingReportLines = billingReportLineStream
+ .collect(Collectors.toList());
+ updateAzureBillingData(billingReportLines);
+ }
+ }
+
+ private BillingReportLine getOrDefault(Map<String, BillingReportLine> billableResources, String tag) {
+ return billableResources.getOrDefault(tag, BillingReportLine.builder().dlabId(tag).build());
+ }
+
+ private void updateGcpBillingData(String endpointName, Map<String, List<BillingReportLine>> billingData) {
+ billingData.forEach((usageDate, billingReportLines) -> {
+ billingDAO.deleteByUsageDateRegex(endpointName, usageDate);
+ billingDAO.save(billingReportLines);
+ });
+ }
+
+ private void updateAwsBillingData(String endpointName, Map<String, List<BillingReportLine>> billingData) {
+ billingData.forEach((usageDate, billingReportLines) -> {
+ billingDAO.deleteByUsageDate(endpointName, usageDate);
+ billingDAO.save(billingReportLines);
+ });
+ }
+
+ private void updateAzureBillingData(List<BillingReportLine> billingReportLines) {
+ billingDAO.save(billingReportLines);
+ }
+
+ private List<BillingData> getBillingData(UserInfo userInfo, EndpointDTO e) {
+ try {
+ return provisioningService.get(getBillingUrl(e.getUrl(), BILLING_PATH), userInfo.getAccessToken(),
new GenericType<List<BillingData>>() {
- }, Collections.singletonMap("dlabIds", String.join(",", dlabIds)));
- } catch (Exception e) {
- log.error("Cannot retrieve billing information for {} {}", dlabIds, e.getMessage());
- return Collections.emptyList();
- }
- }
-
- private List<BillingData> getRemoteBillingData(UserInfo userInfo, BillingFilter filter) {
- List<EndpointDTO> endpoints = endpointService.getEndpoints();
- ExecutorService executor = Executors.newFixedThreadPool(endpoints.size());
- List<Callable<List<BillingData>>> callableTasks = new ArrayList<>();
- endpoints.forEach(e -> callableTasks.add(getTask(userInfo, getBillingUrl(e.getUrl(), BILLING_REPORT_PATH), filter)));
-
- List<BillingData> billingData;
- try {
- log.debug("Trying to retrieve billing info for {}", endpoints);
- billingData = executor.invokeAll(callableTasks)
- .stream()
- .map(this::getBillingReportDTOS)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
- } catch (Exception e) {
- executor.shutdown();
- log.error("Cannot retrieve billing information {}", e.getMessage(), e);
- throw new DlabException("Cannot retrieve billing information", e);
- }
- executor.shutdown();
- return billingData;
- }
-
- private List<BillingData> getBillingReportDTOS(Future<List<BillingData>> s) {
- try {
- return s.get();
- } catch (InterruptedException | ExecutionException e) {
- log.error("Cannot retrieve billing information {}", e.getMessage());
+ });
+ } catch (Exception ex) {
+ log.error("Cannot retrieve billing information for {}. {}", e.getName(), ex.getMessage());
return Collections.emptyList();
}
}
@@ -262,25 +283,25 @@
.toString();
}
- private Callable<List<BillingData>> getTask(UserInfo userInfo, String url, BillingFilter filter) {
- return () -> provisioningService.get(url, userInfo.getAccessToken(),
- new GenericType<List<BillingData>>() {
- },
- Stream.of(new String[][]{
- {"date-start", filter.getDateStart()},
- {"date-end", filter.getDateEnd()},
- {"dlab-id", filter.getDlabId()},
- {"product", String.join(",", filter.getProducts())}
- }).collect(Collectors.toMap(data -> data[0], data -> data[1])));
- }
-
- private Predicate<BillingReportLine> getBillingReportFilter(BillingFilter filter) {
- return br ->
- (CollectionUtils.isEmpty(filter.getUsers()) || filter.getUsers().contains(br.getUser())) &&
- (CollectionUtils.isEmpty(filter.getProjects()) || filter.getProjects().contains(br.getProject())) &&
- (CollectionUtils.isEmpty(filter.getResourceTypes()) || filter.getResourceTypes().contains(String.valueOf(br.getResourceType()))) &&
- (CollectionUtils.isEmpty(filter.getStatuses()) || filter.getStatuses().contains(br.getStatus())) &&
- (CollectionUtils.isEmpty(filter.getShapes()) || filter.getShapes().contains(br.getShape()));
+ private void appendStatuses(BillingReportLine br) {
+ BillingResourceType resourceType = br.getResourceType();
+ if (BillingResourceType.EDGE == resourceType) {
+ projectService.get(br.getProject()).getEndpoints()
+ .stream()
+ .filter(e -> e.getName().equals(br.getResourceName()))
+ .findAny()
+ .ifPresent(e -> br.setStatus(e.getStatus()));
+ } else if (BillingResourceType.EXPLORATORY == resourceType) {
+ exploratoryService.getUserInstance(br.getUser(), br.getProject(), br.getResourceName())
+ .ifPresent(ui -> br.setStatus(UserInstanceStatus.of(ui.getStatus())));
+ } else if (BillingResourceType.COMPUTATIONAL == resourceType) {
+ exploratoryService.getUserInstance(br.getUser(), br.getProject(), br.getExploratoryName(), true)
+ .flatMap(ui -> ui.getResources()
+ .stream()
+ .filter(cr -> cr.getComputationalName().equals(br.getResourceName()))
+ .findAny())
+ .ifPresent(cr -> br.setStatus(UserInstanceStatus.of(cr.getStatus())));
+ }
}
private boolean isFullReport(UserInfo userInfo) {
@@ -294,21 +315,24 @@
}
}
- private BillingReportLine toBillingData(BillingData billingData, BillingReportLine billingReportLine) {
+ private BillingReportLine toBillingReport(BillingData billingData, BillingReportLine billingReportLine) {
return BillingReportLine.builder()
+ .application(billingData.getApplication())
.cost(billingData.getCost())
.currency(billingData.getCurrency())
.product(billingData.getProduct())
.project(billingReportLine.getProject())
- .usageDateTo(billingData.getUsageDateTo())
+ .endpoint(billingReportLine.getEndpoint())
.usageDateFrom(billingData.getUsageDateFrom())
+ .usageDateTo(billingData.getUsageDateTo())
+ .usageDate(billingData.getUsageDate())
.usageType(billingData.getUsageType())
.user(billingReportLine.getUser())
.dlabId(billingData.getTag())
.resourceType(billingReportLine.getResourceType())
.resourceName(billingReportLine.getResourceName())
- .status(billingReportLine.getStatus())
.shape(billingReportLine.getShape())
+ .exploratoryName(billingReportLine.getExploratoryName())
.build();
}
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
index 9c71f76..645f84b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
@@ -138,7 +138,7 @@
cloudProvider = response.readEntity(CloudProvider.class);
} catch (Exception e) {
log.error("Cannot connect to url '{}'. {}", url, e.getMessage());
- throw new DlabException(String.format("Cannot connect to url '%s'", url), e);
+ throw new DlabException(String.format("Cannot connect to url '%s'. %s", url, e.getMessage()));
}
if (response.getStatus() != 200) {
log.warn("Endpoint url {} is not valid", url);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ExploratoryServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ExploratoryServiceImpl.java
index 6a2a615..9f6be91 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ExploratoryServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ExploratoryServiceImpl.java
@@ -189,7 +189,17 @@
try {
return Optional.of(exploratoryDAO.fetchExploratoryFields(user, project, exploratoryName));
} catch (DlabException e) {
- log.warn("User instance with exploratory name {} for user {} not found.", exploratoryName, user);
+ log.warn("User instance with exploratory {}, project {} for user {} not found.", exploratoryName, project, user);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<UserInstanceDTO> getUserInstance(String user, String project, String exploratoryName, boolean includeCompResources) {
+ try {
+ return Optional.of(exploratoryDAO.fetchExploratoryFields(user, project, exploratoryName, includeCompResources));
+ } catch (DlabException e) {
+ log.warn("User instance with exploratory {}, project {} for user {} not found.", exploratoryName, project, user);
}
return Optional.empty();
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImpl.java
index 85ce534..5cb3a64 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImpl.java
@@ -24,9 +24,11 @@
import com.epam.dlab.backendapi.dao.ExploratoryLibDAO;
import com.epam.dlab.backendapi.dao.ImageExploratoryDao;
import com.epam.dlab.backendapi.domain.EndpointDTO;
+import com.epam.dlab.backendapi.domain.ProjectDTO;
import com.epam.dlab.backendapi.resources.dto.ImageInfoRecord;
import com.epam.dlab.backendapi.service.EndpointService;
import com.epam.dlab.backendapi.service.ImageExploratoryService;
+import com.epam.dlab.backendapi.service.ProjectService;
import com.epam.dlab.backendapi.util.RequestBuilder;
import com.epam.dlab.constants.ServiceConsts;
import com.epam.dlab.dto.UserInstanceDTO;
@@ -71,10 +73,12 @@
private RequestBuilder requestBuilder;
@Inject
private EndpointService endpointService;
+ @Inject
+ private ProjectService projectService;
@Override
public String createImage(UserInfo user, String project, String exploratoryName, String imageName, String imageDescription) {
-
+ ProjectDTO projectDTO = projectService.get(project);
UserInstanceDTO userInstance = exploratoryDAO.fetchRunningExploratoryFields(user.getName(), project, exploratoryName);
if (imageExploratoryDao.exist(imageName, userInstance.getProject())) {
@@ -105,7 +109,7 @@
EndpointDTO endpointDTO = endpointService.get(userInstance.getEndpoint());
return provisioningService.post(endpointDTO.getUrl() + ExploratoryAPI.EXPLORATORY_IMAGE,
user.getAccessToken(),
- requestBuilder.newExploratoryImageCreate(user, userInstance, imageName, endpointDTO), String.class);
+ requestBuilder.newExploratoryImageCreate(user, userInstance, imageName, endpointDTO, projectDTO), String.class);
}
@Override
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/InfrastructureInfoServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
index 6d46d71..b1eac51 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
@@ -22,8 +22,8 @@
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.conf.SelfServiceApplicationConfiguration;
import com.epam.dlab.backendapi.dao.BillingDAO;
-import com.epam.dlab.backendapi.dao.EnvDAO;
import com.epam.dlab.backendapi.dao.ExploratoryDAO;
+import com.epam.dlab.backendapi.domain.BillingReport;
import com.epam.dlab.backendapi.domain.EndpointDTO;
import com.epam.dlab.backendapi.domain.ProjectEndpointDTO;
import com.epam.dlab.backendapi.resources.dto.HealthStatusEnum;
@@ -38,7 +38,6 @@
import com.epam.dlab.dto.aws.edge.EdgeInfoAws;
import com.epam.dlab.dto.azure.edge.EdgeInfoAzure;
import com.epam.dlab.dto.base.edge.EdgeInfo;
-import com.epam.dlab.dto.billing.BillingData;
import com.epam.dlab.dto.gcp.edge.EdgeInfoGcp;
import com.epam.dlab.exceptions.DlabException;
import com.google.inject.Inject;
@@ -46,13 +45,11 @@
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
+import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -62,7 +59,6 @@
private static final String RELEASE_NOTES_FORMAT = "https://github.com/apache/incubator-dlab/blob/%s" +
"/RELEASE_NOTES.md";
private final ExploratoryDAO expDAO;
- private final EnvDAO envDAO;
private final SelfServiceApplicationConfiguration configuration;
private final BillingDAO billingDAO;
private final ProjectService projectService;
@@ -70,11 +66,10 @@
private final BillingService billingService;
@Inject
- public InfrastructureInfoServiceImpl(ExploratoryDAO expDAO, EnvDAO envDAO, SelfServiceApplicationConfiguration configuration,
+ public InfrastructureInfoServiceImpl(ExploratoryDAO expDAO, SelfServiceApplicationConfiguration configuration,
BillingDAO billingDAO, ProjectService projectService, EndpointService endpointService,
BillingService billingService) {
this.expDAO = expDAO;
- this.envDAO = envDAO;
this.configuration = configuration;
this.billingDAO = billingDAO;
this.projectService = projectService;
@@ -88,8 +83,7 @@
try {
Iterable<Document> documents = expDAO.findExploratory(user.getName());
List<EndpointDTO> allEndpoints = endpointService.getEndpoints();
- return StreamSupport.stream(documents.spliterator(),
- false)
+ return StreamSupport.stream(documents.spliterator(), false)
.collect(Collectors.groupingBy(d -> d.getString("project")))
.entrySet()
.stream()
@@ -100,27 +94,24 @@
.anyMatch(endpoint1 -> endpoint1.getName().equals(endpoint.getName())))
.collect(Collectors.toList());
- List<BillingData> collect = e.getValue()
+ List<BillingReport> billingData = e.getValue()
.stream()
- .map(exp -> {
- List<BillingData> exploratoryRemoteBillingData = new ArrayList<>();
- try {
- exploratoryRemoteBillingData = billingService.getExploratoryRemoteBillingData(user, (String) exp.get("endpoint"),
- expDAO.findExploratories(e.getKey(), (String) exp.get("endpoint"), user.getName()));
- } catch (Exception ex) {
- log.error("Cannot retrieve billing information", ex);
- }
- return exploratoryRemoteBillingData;
- })
- .flatMap(Collection::stream)
+ .map(exp ->
+ billingService.getExploratoryBillingData(exp.getString("project"), exp.getString("endpoint"),
+ exp.getString("exploratory_name"),
+ Optional.ofNullable(exp.get("computational_resources")).map(cr -> (List<Document>) cr).get()
+ .stream()
+ .map(cr -> cr.getString("computational_name"))
+ .collect(Collectors.toList()))
+ )
.collect(Collectors.toList());
final Map<String, Map<String, String>> projectEdges =
- endpoints.stream()
- .collect(Collectors.toMap(ProjectEndpointDTO::getName,
- endpointDTO -> getSharedInfo(endpointDTO.getEdgeInfo())));
- return new ProjectInfrastructureInfo(e.getKey(),
- billingDAO.getBillingProjectQuoteUsed(e.getKey()), projectEdges, e.getValue(), collect, endpointResult);
+ endpoints
+ .stream()
+ .collect(Collectors.toMap(ProjectEndpointDTO::getName, this::getSharedInfo));
+ return new ProjectInfrastructureInfo(e.getKey(), billingDAO.getBillingProjectQuoteUsed(e.getKey()),
+ projectEdges, e.getValue(), billingData, endpointResult);
})
.collect(Collectors.toList());
} catch (Exception e) {
@@ -161,18 +152,22 @@
.build();
}
- private Map<String, String> getSharedInfo(EdgeInfo edgeInfo) {
- Map<String, String> shared = new HashMap<>();
- if (Objects.isNull(edgeInfo)) {
- return shared;
+ private Map<String, String> getSharedInfo(ProjectEndpointDTO endpointDTO) {
+ Optional<EdgeInfo> edgeInfo = Optional.ofNullable(endpointDTO.getEdgeInfo());
+ if (!edgeInfo.isPresent()) {
+ return Collections.emptyMap();
}
- shared.put("edge_node_ip", edgeInfo.getPublicIp());
- if (edgeInfo instanceof EdgeInfoAws) {
- EdgeInfoAws edgeInfoAws = (EdgeInfoAws) edgeInfo;
+ EdgeInfo edge = edgeInfo.get();
+ Map<String, String> shared = new HashMap<>();
+
+ shared.put("status", endpointDTO.getStatus().toString());
+ shared.put("edge_node_ip", edge.getPublicIp());
+ if (edge instanceof EdgeInfoAws) {
+ EdgeInfoAws edgeInfoAws = (EdgeInfoAws) edge;
shared.put("user_own_bicket_name", edgeInfoAws.getUserOwnBucketName());
shared.put("shared_bucket_name", edgeInfoAws.getSharedBucketName());
- } else if (edgeInfo instanceof EdgeInfoAzure) {
- EdgeInfoAzure edgeInfoAzure = (EdgeInfoAzure) edgeInfo;
+ } else if (edge instanceof EdgeInfoAzure) {
+ EdgeInfoAzure edgeInfoAzure = (EdgeInfoAzure) edge;
shared.put("user_container_name", edgeInfoAzure.getUserContainerName());
shared.put("shared_container_name", edgeInfoAzure.getSharedContainerName());
shared.put("user_storage_account_name", edgeInfoAzure.getUserStorageAccountName());
@@ -180,8 +175,8 @@
shared.put("datalake_name", edgeInfoAzure.getDataLakeName());
shared.put("datalake_user_directory_name", edgeInfoAzure.getDataLakeDirectoryName());
shared.put("datalake_shared_directory_name", edgeInfoAzure.getDataLakeSharedDirectoryName());
- } else if (edgeInfo instanceof EdgeInfoGcp) {
- EdgeInfoGcp edgeInfoGcp = (EdgeInfoGcp) edgeInfo;
+ } else if (edge instanceof EdgeInfoGcp) {
+ EdgeInfoGcp edgeInfoGcp = (EdgeInfoGcp) edge;
shared.put("user_own_bucket_name", edgeInfoGcp.getUserOwnBucketName());
shared.put("shared_bucket_name", edgeInfoGcp.getSharedBucketName());
}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/UserGroupServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/UserGroupServiceImpl.java
index 1758a8b..9eb25c3 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/UserGroupServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/UserGroupServiceImpl.java
@@ -24,6 +24,7 @@
import com.epam.dlab.backendapi.dao.UserRoleDao;
import com.epam.dlab.backendapi.domain.ProjectDTO;
import com.epam.dlab.backendapi.resources.dto.UserGroupDto;
+import com.epam.dlab.backendapi.resources.dto.UserRoleDto;
import com.epam.dlab.backendapi.roles.UserRoles;
import com.epam.dlab.backendapi.service.ProjectService;
import com.epam.dlab.backendapi.service.UserGroupService;
@@ -45,6 +46,8 @@
@Slf4j
public class UserGroupServiceImpl implements UserGroupService {
private static final String ROLE_NOT_FOUND_MSG = "Any of role : %s were not found";
+ private static final String ADMIN = "admin";
+ private static final String PROJECT_ADMIN = "projectAdmin";
@Inject
private UserGroupDao userGroupDao;
@@ -97,22 +100,30 @@
@Override
public List<UserGroupDto> getAggregatedRolesByGroup(UserInfo user) {
if (UserRoles.isAdmin(user)) {
- return userRoleDao.aggregateRolesByGroup(true);
+ return userRoleDao.aggregateRolesByGroup();
} else if (UserRoles.isProjectAdmin(user)) {
Set<String> groups = projectService.getProjects(user)
.stream()
.map(ProjectDTO::getGroups)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
- return userRoleDao.aggregateRolesByGroup(false)
+ return userRoleDao.aggregateRolesByGroup()
.stream()
- .filter(userGroup -> groups.contains(userGroup.getGroup()))
+ .filter(userGroup -> groups.contains(userGroup.getGroup()) && !containsAdministrationPermissions(userGroup))
.collect(Collectors.toList());
} else {
throw new DlabException(String.format("User %s doesn't have appropriate permission", user.getName()));
}
}
+ private boolean containsAdministrationPermissions(UserGroupDto userGroup) {
+ List<String> ids = userGroup.getRoles()
+ .stream()
+ .map(UserRoleDto::getId)
+ .collect(Collectors.toList());
+ return ids.contains(ADMIN) || ids.contains(PROJECT_ADMIN);
+ }
+
private void updateGroup(String group, Set<String> roleIds, Set<String> users) {
log.debug("Updating users for group {}: {}", group, users);
userGroupDao.updateUsers(group, users);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/util/BillingUtils.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/util/BillingUtils.java
index 2c5b87f..7e46a09 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/util/BillingUtils.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/util/BillingUtils.java
@@ -47,7 +47,8 @@
import static com.epam.dlab.dto.billing.BillingResourceType.VOLUME;
public class BillingUtils {
- private static final String[] REPORT_HEADERS = {"DLab ID", "User", "Project", "DLab Resource Type", "Shape", "Product", "Cost"};
+ private static final String[] AVAILABLE_NOTEBOOKS = {"zeppelin", "tensor-rstudio", "rstudio", "tensor", "superset", "jupyterlab", "jupyter", "deeplearning"};
+ private static final String[] REPORT_HEADERS = {"DLab ID", "User", "Project", "DLab Resource Type", "Status", "Shape", "Product", "Cost"};
private static final String REPORT_FIRST_LINE = "Service base name: %s. Available reporting period from: %s to: %s";
private static final String TOTAL_LINE = "Total: %s %s";
private static final String SSN_FORMAT = "%s-ssn";
@@ -63,25 +64,24 @@
private static final String IMAGE_STANDARD_FORMAT1 = "%s-%s-%s-%s-notebook-image";
private static final String IMAGE_STANDARD_FORMAT2 = "%s-%s-%s-notebook-image";
private static final String IMAGE_CUSTOM_FORMAT = "%s-%s-%s-%s-%s";
- private static final String IMAGE_NAME_PREFIX = "docker.dlab-";
- private static final String VOLUME_PRIMARY = "Volume primary";
- private static final String VOLUME_SECONDARY = "Volume secondary";
private static final String SHARED_RESOURCE = "Shared resource";
private static final String IMAGE_NAME = "Image";
private static final String DATAENGINE_NAME_FORMAT = "%d x %s";
- private static final String DATAENGINE_SERVICE_NAME_FORMAT = "Master: %s%sSlave: %d x %s";
+ private static final String DATAENGINE_SERVICE_NAME_FORMAT = "Master: %sSlave: %s";
- public static Stream<BillingReportLine> edgeBillingDataStream(String project, String sbn, String endpoint, String status) {
- final String userEdgeId = String.format(EDGE_FORMAT, sbn, project.toLowerCase(), endpoint);
- final String edgeVolumeId = String.format(EDGE_VOLUME_FORMAT, sbn, project.toLowerCase(), endpoint);
- final String endpointBucketId = String.format(PROJECT_ENDPOINT_BUCKET_FORMAT, sbn, project.toLowerCase(), endpoint);
+ public static Stream<BillingReportLine> edgeBillingDataStream(String project, String sbn, String endpoint) {
+ final String userEdgeId = String.format(EDGE_FORMAT, sbn, project, endpoint).toLowerCase();
+ final String edgeVolumeId = String.format(EDGE_VOLUME_FORMAT, sbn, project, endpoint).toLowerCase();
+ final String endpointBucketId = String.format(PROJECT_ENDPOINT_BUCKET_FORMAT, sbn, project, endpoint).toLowerCase();
- return Stream.of(
- BillingReportLine.builder().resourceName("EDGE node").user(SHARED_RESOURCE).project(project).dlabId(userEdgeId).resourceType(EDGE).status(UserInstanceStatus.of(status)).build(),
+ return Stream.concat(Stream.of(
+ BillingReportLine.builder().resourceName(endpoint).user(SHARED_RESOURCE).project(project).dlabId(userEdgeId).resourceType(EDGE).build(),
BillingReportLine.builder().resourceName("EDGE volume").user(SHARED_RESOURCE).project(project).dlabId(edgeVolumeId).resourceType(VOLUME).build(),
BillingReportLine.builder().resourceName("Project endpoint shared bucket").user(SHARED_RESOURCE).project(project).dlabId(endpointBucketId).resourceType(BUCKET).build()
+ ),
+ standardImageBillingDataStream(sbn, project, endpoint)
);
}
@@ -94,76 +94,92 @@
}
public static Stream<BillingReportLine> sharedEndpointBillingDataStream(String endpoint, String sbn) {
- final String projectEndpointBucketId = String.format(ENDPOINT_SHARED_BUCKET_FORMAT, sbn, endpoint.toLowerCase());
- final String endpointId = String.format(ENDPOINT_FORMAT, sbn, endpoint.toLowerCase());
- return Stream.of(
+ final String projectEndpointBucketId = String.format(ENDPOINT_SHARED_BUCKET_FORMAT, sbn, endpoint).toLowerCase();
+ final String endpointId = String.format(ENDPOINT_FORMAT, sbn, endpoint).toLowerCase();
+ return Stream.concat(Stream.of(
BillingReportLine.builder().resourceName("Endpoint shared bucket").user(SHARED_RESOURCE).project(SHARED_RESOURCE).dlabId(projectEndpointBucketId).resourceType(BUCKET).build(),
BillingReportLine.builder().resourceName("Endpoint").user(SHARED_RESOURCE).project(SHARED_RESOURCE).dlabId(endpointId).resourceType(ENDPOINT).build()
- );
+ ),
+ standardImageBillingDataStream(sbn, endpoint));
}
- public static Stream<BillingReportLine> exploratoryBillingDataStream(UserInstanceDTO userInstance, Integer maxSparkInstanceCount, String sbn) {
+ public static Stream<BillingReportLine> exploratoryBillingDataStream(UserInstanceDTO userInstance, Integer maxSparkInstanceCount) {
final Stream<BillingReportLine> computationalStream = userInstance.getResources()
.stream()
.filter(cr -> cr.getComputationalId() != null)
- .flatMap(cr -> Stream.concat(Stream.of(
- withUserProject(userInstance).dlabId(cr.getComputationalId()).resourceName(cr.getComputationalName()).resourceType(COMPUTATIONAL)
- .status(UserInstanceStatus.of(cr.getStatus())).shape(getComputationalShape(cr)).build(),
- withUserProject(userInstance).resourceName(cr.getComputationalName() + ":" + VOLUME_PRIMARY).dlabId(String.format(VOLUME_PRIMARY_COMPUTATIONAL_FORMAT, cr.getComputationalId(), "m"))
- .resourceType(VOLUME).build(),
- withUserProject(userInstance).resourceName(cr.getComputationalName() + ":" + VOLUME_SECONDARY).dlabId(String.format(VOLUME_SECONDARY_COMPUTATIONAL_FORMAT, cr.getComputationalId(), "m"))
- .resourceType(VOLUME).build()
- ),
- getSlaveVolumes(userInstance, cr, maxSparkInstanceCount)
- ));
- final String exploratoryId = userInstance.getExploratoryId();
- final String imageId1 = String.format(IMAGE_STANDARD_FORMAT1, sbn, userInstance.getProject(), userInstance.getEndpoint(), userInstance.getImageName().replace(IMAGE_NAME_PREFIX, ""));
- final String imageId2 = String.format(IMAGE_STANDARD_FORMAT2, sbn, userInstance.getEndpoint(), userInstance.getImageName().replace(IMAGE_NAME_PREFIX, ""));
+ .flatMap(cr -> {
+ final String computationalId = cr.getComputationalId().toLowerCase();
+ return Stream.concat(Stream.of(
+ withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(computationalId).resourceType(COMPUTATIONAL).shape(getComputationalShape(cr))
+ .exploratoryName(userInstance.getExploratoryName()).build(),
+ withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_PRIMARY_FORMAT, computationalId)).resourceType(VOLUME).build(),
+ withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_SECONDARY_FORMAT, computationalId)).resourceType(VOLUME).build(),
+ withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_PRIMARY_COMPUTATIONAL_FORMAT, computationalId, "m"))
+ .resourceType(VOLUME).build(),
+ withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_SECONDARY_COMPUTATIONAL_FORMAT, computationalId, "m"))
+ .resourceType(VOLUME).build()
+ ),
+ getSlaveVolumes(userInstance, cr, maxSparkInstanceCount)
+ );
+ });
+ final String exploratoryName = userInstance.getExploratoryName();
+ final String exploratoryId = userInstance.getExploratoryId().toLowerCase();
final String primaryVolumeId = String.format(VOLUME_PRIMARY_FORMAT, exploratoryId);
final String secondaryVolumeId = String.format(VOLUME_SECONDARY_FORMAT, exploratoryId);
final Stream<BillingReportLine> exploratoryStream = Stream.of(
- withUserProject(userInstance).resourceName(userInstance.getExploratoryName()).dlabId(exploratoryId).resourceType(EXPLORATORY).status(UserInstanceStatus.of(userInstance.getStatus())).shape(userInstance.getShape()).build(),
- BillingReportLine.builder().resourceName(IMAGE_NAME).dlabId(imageId1).project(SHARED_RESOURCE).resourceType(IMAGE).build(),
- BillingReportLine.builder().resourceName(IMAGE_NAME).dlabId(imageId2).project(SHARED_RESOURCE).resourceType(IMAGE).build(),
- withUserProject(userInstance).resourceName(VOLUME_PRIMARY).dlabId(primaryVolumeId).resourceType(VOLUME).build(),
- withUserProject(userInstance).resourceName(VOLUME_SECONDARY).dlabId(secondaryVolumeId).resourceType(VOLUME).build());
+ withUserProjectEndpoint(userInstance).resourceName(exploratoryName).dlabId(exploratoryId).resourceType(EXPLORATORY).shape(userInstance.getShape()).build(),
+ withUserProjectEndpoint(userInstance).resourceName(exploratoryName).dlabId(primaryVolumeId).resourceType(VOLUME).build(),
+ withUserProjectEndpoint(userInstance).resourceName(exploratoryName).dlabId(secondaryVolumeId).resourceType(VOLUME).build());
+
return Stream.concat(computationalStream, exploratoryStream);
}
public static Stream<BillingReportLine> customImageBillingDataStream(ImageInfoRecord image, String sbn) {
- String imageId = String.format(IMAGE_CUSTOM_FORMAT, sbn, image.getProject(), image.getEndpoint(), image.getApplication(), image.getName());
+ String imageId = String.format(IMAGE_CUSTOM_FORMAT, sbn, image.getProject(), image.getEndpoint(), image.getApplication(), image.getName()).toLowerCase();
return Stream.of(
- BillingReportLine.builder().resourceName(IMAGE_NAME).project(image.getProject()).dlabId(imageId).resourceType(IMAGE).build()
+ BillingReportLine.builder().resourceName(image.getName()).project(image.getProject()).dlabId(imageId).user(image.getUser()).resourceType(IMAGE).build()
);
}
private static Stream<BillingReportLine> getSlaveVolumes(UserInstanceDTO userInstance, UserComputationalResource cr, Integer maxSparkInstanceCount) {
List<BillingReportLine> list = new ArrayList<>();
for (int i = 1; i <= maxSparkInstanceCount; i++) {
- list.add(withUserProject(userInstance).resourceName(cr.getComputationalName() + ":" + VOLUME_PRIMARY).dlabId(String.format(VOLUME_PRIMARY_COMPUTATIONAL_FORMAT, cr.getComputationalId(), "s" + i))
+ list.add(withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_PRIMARY_COMPUTATIONAL_FORMAT, cr.getComputationalId().toLowerCase(), "s" + i))
.resourceType(VOLUME).build());
- list.add(withUserProject(userInstance).resourceName(cr.getComputationalName() + ":" + VOLUME_PRIMARY).dlabId(String.format(VOLUME_SECONDARY_COMPUTATIONAL_FORMAT, cr.getComputationalId(), "s" + i))
+ list.add(withUserProjectEndpoint(userInstance).resourceName(cr.getComputationalName()).dlabId(String.format(VOLUME_SECONDARY_COMPUTATIONAL_FORMAT, cr.getComputationalId().toLowerCase(), "s" + i))
.resourceType(VOLUME).build());
}
return list.stream();
}
- private static BillingReportLine.BillingReportLineBuilder withUserProject(UserInstanceDTO userInstance) {
- return BillingReportLine.builder().user(userInstance.getUser()).project(userInstance.getProject());
+ private static BillingReportLine.BillingReportLineBuilder withUserProjectEndpoint(UserInstanceDTO userInstance) {
+ return BillingReportLine.builder().user(userInstance.getUser()).project(userInstance.getProject()).endpoint(userInstance.getEndpoint());
}
- public static List<String> getComputationalIds(String computationalId) {
- return Arrays.asList(computationalId, String.format(VOLUME_PRIMARY_FORMAT, computationalId));
- }
-
- public static List<String> getExploratoryIds(String exploratoryId) {
- return Arrays.asList(exploratoryId, String.format(VOLUME_PRIMARY_FORMAT, exploratoryId), String.format(VOLUME_SECONDARY_FORMAT, exploratoryId));
- }
-
- private static String getComputationalShape(UserComputationalResource resource) {
+ public static String getComputationalShape(UserComputationalResource resource) {
return DataEngineType.fromDockerImageName(resource.getImageName()) == DataEngineType.SPARK_STANDALONE ?
String.format(DATAENGINE_NAME_FORMAT, resource.getDataengineInstanceCount(), resource.getDataengineShape()) :
- String.format(DATAENGINE_SERVICE_NAME_FORMAT, resource.getMasterNodeShape(), System.lineSeparator(), null, null);
+ String.format(DATAENGINE_SERVICE_NAME_FORMAT, resource.getMasterNodeShape(), resource.getSlaveNodeShape());
+ }
+
+ private static Stream<BillingReportLine> standardImageBillingDataStream(String sbn, String endpoint) {
+ List<BillingReportLine> list = new ArrayList<>();
+ for (String notebook : AVAILABLE_NOTEBOOKS) {
+ list.add(BillingReportLine.builder().resourceName(IMAGE_NAME).dlabId(String.format(IMAGE_STANDARD_FORMAT2, sbn, endpoint, notebook).toLowerCase())
+ .user(SHARED_RESOURCE).project(SHARED_RESOURCE).resourceType(IMAGE).build());
+ }
+
+ return list.stream();
+ }
+
+ private static Stream<BillingReportLine> standardImageBillingDataStream(String sbn, String project, String endpoint) {
+ List<BillingReportLine> list = new ArrayList<>();
+ for (String notebook : AVAILABLE_NOTEBOOKS) {
+ list.add(BillingReportLine.builder().resourceName(IMAGE_NAME).dlabId(String.format(IMAGE_STANDARD_FORMAT1, sbn, project, endpoint, notebook).toLowerCase())
+ .project(project).user(SHARED_RESOURCE).resourceType(IMAGE).build());
+ }
+
+ return list.stream();
}
public static String getFirstLine(String sbn, LocalDate from, LocalDate to) {
@@ -173,16 +189,23 @@
CSVFormatter.SEPARATOR, '\"');
}
- public static String getHeader() {
- return CSVFormatter.formatLine(new ArrayList<>(Arrays.asList(BillingUtils.REPORT_HEADERS)), CSVFormatter.SEPARATOR);
+ public static String getHeader(boolean isFull) {
+ List<String> headers = new ArrayList<>(Arrays.asList(BillingUtils.REPORT_HEADERS));
+ if (!isFull) {
+ headers.remove(1);
+ }
+ return CSVFormatter.formatLine(headers, CSVFormatter.SEPARATOR);
}
- public static String printLine(BillingReportLine line) {
+ public static String printLine(BillingReportLine line, boolean isFull) {
List<String> lines = new ArrayList<>();
lines.add(getOrEmpty(line.getDlabId()));
- lines.add(getOrEmpty(line.getUser()));
+ if (isFull) {
+ lines.add(getOrEmpty(line.getUser()));
+ }
lines.add(getOrEmpty(line.getProject()));
- lines.add(getOrEmpty(Optional.ofNullable(line.getResourceType()).map(Enum::name).orElse(null)));
+ lines.add(getOrEmpty(Optional.ofNullable(line.getResourceType()).map(r -> StringUtils.capitalize(r.toString().toLowerCase())).orElse(null)));
+ lines.add(getOrEmpty(Optional.ofNullable(line.getStatus()).map(UserInstanceStatus::toString).orElse(null)));
lines.add(getOrEmpty(line.getShape()));
lines.add(getOrEmpty(line.getProduct()));
lines.add(getOrEmpty(Optional.ofNullable(line.getCost()).map(String::valueOf).orElse(null)));
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/util/RequestBuilder.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/util/RequestBuilder.java
index 69aeb6c..afe06cd 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/util/RequestBuilder.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/util/RequestBuilder.java
@@ -509,7 +509,7 @@
@SuppressWarnings("unchecked")
public <T extends ExploratoryImageDTO> T newExploratoryImageCreate(UserInfo userInfo, UserInstanceDTO userInstance,
- String imageName, EndpointDTO endpointDTO) {
+ String imageName, EndpointDTO endpointDTO, ProjectDTO projectDTO) {
checkInappropriateCloudProviderOrElseThrowException(endpointDTO.getCloudProvider());
return (T) newResourceSysBaseDTO(userInfo, endpointDTO.getCloudProvider(), ExploratoryImageDTO.class)
.withProject(userInstance.getProject())
@@ -519,7 +519,8 @@
.withNotebookImage(userInstance.getImageName())
.withImageName(imageName)
.withEndpoint(userInstance.getEndpoint())
- .withTags(userInstance.getTags());
+ .withTags(userInstance.getTags())
+ .withSharedImageEnabled(String.valueOf(projectDTO.isSharedImageEnabled()));
}
@SuppressWarnings("unchecked")
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
index a3ae8d2..f99944f 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
@@ -69,7 +69,7 @@
}
public setBudgetLimits(value) {
- if (this.getCurrentTotalValue() >= this.getCurrentUsersTotal()) {
+ if (this.getCurrentTotalValue() >= this.getCurrentUsersTotal() || !this.getCurrentTotalValue()) {
this.dialogRef.close(value);
} else {
this.manageUsersForm.controls['total'].setErrors({ overrun: true });
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
index 631e7ae..9947516 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.html
@@ -20,7 +20,7 @@
<div class="ani">
<table mat-table [dataSource]="allFilteredEnvironmentData" class="data-grid management mat-elevation-z6">
<ng-container matColumnDef="user">
- <th mat-header-cell *matHeaderCellDef class="user">
+ <th mat-header-cell *matHeaderCellDef class="user label-header">
<span class="label">User</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -32,7 +32,7 @@
</ng-container>
<ng-container matColumnDef="project">
- <th mat-header-cell *matHeaderCellDef class="project">
+ <th mat-header-cell *matHeaderCellDef class="project label-header">
<span class="label">Project</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -44,7 +44,7 @@
</ng-container>
<ng-container matColumnDef="type">
- <th mat-header-cell *matHeaderCellDef class="type">
+ <th mat-header-cell *matHeaderCellDef class="type label-header">
<span class="label">Type</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -56,7 +56,7 @@
</ng-container>
<ng-container matColumnDef="shape">
- <th mat-header-cell *matHeaderCellDef class="shape">
+ <th mat-header-cell *matHeaderCellDef class="shape label-header">
<span class="label">Shape / Resource id</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -68,7 +68,7 @@
</ng-container>
<ng-container matColumnDef="status">
- <th mat-header-cell *matHeaderCellDef class="status">
+ <th mat-header-cell *matHeaderCellDef class="status label-header">
<span class="label">Status</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
@@ -77,13 +77,13 @@
<span [hidden]="filtering && filterForm.statuses.length > 0 && !collapsedFilterRow">more_vert</span>
</i>
</button> </th>
- <td mat-cell *matCellDef="let element" class="ani status" >
+ <td mat-cell *matCellDef="let element" class="ani status label-header" >
<span ngClass="{{element.status || ''}}">{{ element.status }}</span>
</td>
</ng-container>
<ng-container matColumnDef="resources">
- <th mat-header-cell *matHeaderCellDef class="resources">
+ <th mat-header-cell *matHeaderCellDef class="resources label-header">
<span class="label">Computational resources</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -122,7 +122,7 @@
</ng-container>
<ng-container matColumnDef="actions">
- <th mat-header-cell *matHeaderCellDef class="actions">
+ <th mat-header-cell *matHeaderCellDef class="actions label-header">
<span class="label"> Actions </span>
</th>
<td mat-cell *matCellDef="let element" class="settings actions-col">
@@ -166,43 +166,43 @@
<!-- FILTERING -->
<ng-container matColumnDef="user-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'users'" [items]="filterConfiguration.users"
[model]="filterForm.users"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="type-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<input placeholder="Filter by environment type" type="text" class="form-control filter-field"
[value]="filterForm.type" (input)="filterForm.type = $event.target.value" />
</th>
</ng-container>
<ng-container matColumnDef="project-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'projects'"
[items]="filterConfiguration.projects" [model]="filterForm.projects"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="shape-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'shapes'"
[items]="filterConfiguration.shapes" [model]="filterForm.shapes"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="status-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'statuses'"
[items]="filterConfiguration.statuses" [model]="filterForm.statuses"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="resource-filter" sticky>
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'resources'"
[items]="filterConfiguration.resources" [model]="filterForm.resources"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="actions-filter" sticky>
- <th mat-header-cell *matHeaderCellDef class="actions-col">
+ <th mat-header-cell *matHeaderCellDef class="actions-col filter-row-item">
<div class="actions">
<button mat-icon-button class="btn reset" (click)="resetFilterConfigurations()">
<i class="material-icons">close</i>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
index 216d79c..d0ab9dc 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/management-grid/management-grid.component.ts
@@ -162,11 +162,11 @@
if (action === 'stop') {
this.dialog.open(ConfirmationDialogComponent, {
- data: { notebook: environment, type: type, manageAction: this.isAdmin }, panelClass: 'modal-md'
+ data: { notebook: environment, type: type, manageAction: true }, panelClass: 'modal-md'
}).afterClosed().subscribe(() => this.buildGrid());
} else if (action === 'terminate') {
this.dialog.open(ConfirmationDialogComponent, {
- data: { notebook: environment, type: ConfirmationDialogType.TerminateExploratory, manageAction: this.isAdmin }, panelClass: 'modal-md'
+ data: { notebook: environment, type: ConfirmationDialogType.TerminateExploratory, manageAction: true }, panelClass: 'modal-md'
}).afterClosed().subscribe(() => this.buildGrid());
} else if (action === 'run') {
this.healthStatusService.runEdgeNode().subscribe(() => {
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
index 9833a40..d7442de 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
@@ -122,13 +122,12 @@
private toggleStatusRequest(data, action) {
if ( action === 'terminate') {
- const projectsResources = this.resources
- .filter(resource => resource.project === data.project_name )[0].exploratory
- .filter(expl => expl.status !== 'terminated' && expl.status !== 'terminating');
-
+ const projectsResources = this.resources.filter(resource => resource.project === data.project_name );
+ const activeProjectsResources = projectsResources.length ? projectsResources[0].exploratory
+ .filter(expl => expl.status !== 'terminated' && expl.status !== 'terminating' && expl.status !== 'failed') : [];
let termResources = [];
data.endpoint.forEach(v => {
- termResources = [...termResources, ...projectsResources.filter(resource => resource.endpoint === v)];
+ termResources = [...termResources, ...activeProjectsResources.filter(resource => resource.endpoint === v)];
});
this.dialog.open(NotificationDialogComponent, { data: {
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
index 87e27a2..5c73329 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html
@@ -36,7 +36,7 @@
<mat-step [completed]='false'>
<ng-template matStepLabel>Groups</ng-template>
<div class="inner-step mat-reset">
- <input [validator]="groupValidarion()" type="text" placeholder="Enter group name" [(ngModel)]="setupGroup"
+ <input [validator]="groupValidation()" type="text" placeholder="Enter group name" [(ngModel)]="setupGroup"
#setupGroupName="ngModel">
<div class="error" *ngIf="setupGroupName.errors?.patterns && setupGroupName.dirty">Group name can only
contain letters, numbers, hyphens and '_'</div>
@@ -72,7 +72,9 @@
(selectionChange)="onUpdate($event)"
name="roles"
[items]="rolesList"
- [model]="setupRoles">
+ [model]="setupRoles"
+ [isAdmin]="healthStatus?.admin"
+ >
</multi-level-select-dropdown>
</div>
</div>
@@ -104,8 +106,9 @@
(selectionChange)="onUpdate($event)"
[type]="element.group"
[items]="rolesList"
- [model]="element.selected_roles">
-
+ [model]="element.selected_roles"
+ [isAdmin]="healthStatus?.admin"
+ >
</multi-level-select-dropdown>
</div>
</td>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
index bf57438..da36c4e 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts
@@ -75,6 +75,9 @@
this.rolesList = roles.map((role) => {
return {role: role.description, type: role.type, cloud: role.cloud};
});
+ if (!this.healthStatus.admin) {
+ this.rolesList = this.rolesList.filter(role => role.role !== 'Allow to execute administration operation');
+ }
this.rolesList = this.rolesList.sort((a, b) => (a.cloud > b.cloud) ? 1 : ((b.cloud > a.cloud) ? -1 : 0));
this.rolesList = this.rolesList.sort((a, b) => (a.type > b.type) ? 1 : ((b.type > a.type) ? -1 : 0));
this.updateGroupData(groups);
@@ -138,10 +141,14 @@
item.selected_roles = [...currGroupSource.selected_roles];
item.roles = [...currGroupSource.roles];
} else {
+ const isSuperAdminGroup = this.startedGroups.filter(v => v.group === item.group)[0].roles.filter(role => role.description === 'Allow to execute administration operation').length;
+ const selectedRoles = isSuperAdminGroup ?
+ [...item.selected_roles.map(v => v.role), 'Allow to execute administration operation'] :
+ item.selected_roles.map(v => v.role);
this.manageRolesGroups({
action, type, value: {
name: item.group,
- roleIds: this.extractIds(this.roles, item.selected_roles.map(v => v.role)),
+ roleIds: this.extractIds(this.roles, selectedRoles),
users: item.users || []
}
});
@@ -208,7 +215,13 @@
return v;
}).sort((a, b) => (a.group > b.group) ? 1 : ((b.group > a.group) ? -1 : 0));
this.groupsData.forEach(item => {
- item.selected_roles = item.roles.map(role => ({role: role.description, type: role.type, cloud: role.cloud}));
+ if (this.healthStatus.admin) {
+ item.selected_roles = item.roles.map(role => ({role: role.description, type: role.type, cloud: role.cloud}));
+ } else {
+ item.selected_roles = item.roles.filter(role => role.description !== 'Allow to execute administration operation')
+ .map(role => ({role: role.description, type: role.type, cloud: role.cloud}));
+ }
+
});
this.getGroupsListCopy();
}
@@ -217,7 +230,7 @@
this.startedGroups = JSON.parse(JSON.stringify(this.groupsData));
}
- public groupValidarion(): ValidatorFn {
+ public groupValidation(): ValidatorFn {
const duplicateList: any = this.groupsData.map(item => item.group.toLowerCase());
return <ValidatorFn>((control: FormControl) => {
if (control.value && duplicateList.includes(CheckUtils.delimitersFiltering(control.value.toLowerCase()))) {
diff --git a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
index 0eb86a4..8413a5d 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.html
@@ -21,7 +21,7 @@
<table mat-table [dataSource]="reportData" class="data-grid reporting mat-elevation-z6">
<ng-container matColumnDef="name">
- <th mat-header-cell *matHeaderCellDef class="env_name">
+ <th mat-header-cell *matHeaderCellDef class="env_name label-header">
<div class="label"><span class="text"> Environment name</span></div>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -30,12 +30,12 @@
</i>
</button>
</th>
- <td mat-cell *matCellDef="let element"> {{element.dlabId}} </td>
+ <td mat-cell *matCellDef="let element"><span class="table-item">{{element.dlabId}}</span></td>
<td mat-footer-cell *matFooterCellDef class="table-footer"></td>
</ng-container>
<ng-container matColumnDef="user">
- <th mat-header-cell *matHeaderCellDef class="th_user">
+ <th mat-header-cell *matHeaderCellDef class="th_user label-header">
<div class="sort">
<div class="sort-arrow up" (click)="sortBy('user', 'down')" [ngClass]="{'active': !!this.active['userdown']}"></div>
<div class="sort-arrow down" (click)="sortBy('user', 'up')" [ngClass]="{'active': !!this.active['userup']}"></div>
@@ -55,7 +55,7 @@
</ng-container>
<ng-container matColumnDef="project">
- <th mat-header-cell *matHeaderCellDef class="th_project">
+ <th mat-header-cell *matHeaderCellDef class="th_project label-header">
<div class="sort">
<div class="sort-arrow up" (click)="sortBy('project', 'down')" [ngClass]="{'active': !!this.active['projectdown']}"></div>
<div class="sort-arrow down" (click)="sortBy('project', 'up')" [ngClass]="{'active': !!this.active['projectup']}"></div>
@@ -73,7 +73,7 @@
</ng-container>
<ng-container matColumnDef="type">
- <th mat-header-cell *matHeaderCellDef class="th_type">
+ <th mat-header-cell *matHeaderCellDef class="th_type label-header">
<div class="label"><span class="text"> Resource Type</span> </div>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -87,7 +87,7 @@
</ng-container>
<ng-container matColumnDef="status">
- <th mat-header-cell *matHeaderCellDef class="th_status">
+ <th mat-header-cell *matHeaderCellDef class="th_status label-header">
<div class="label"><span class="text"> Status</span> </div>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -105,7 +105,7 @@
</ng-container>
<ng-container matColumnDef="shape">
- <th mat-header-cell *matHeaderCellDef class="th_shape">
+ <th mat-header-cell *matHeaderCellDef class="th_shape label-header">
<div class="label"><span class="text"> Instance size</span></div>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -116,13 +116,13 @@
</button>
</th>
<td mat-cell *matCellDef="let element">
- <span>{{element.shape}}</span>
+ <div *ngFor="let shape of shapeSplit(element.shape)">{{shape}}</div>
</td>
<td mat-footer-cell *matFooterCellDef class="table-footer"></td>
</ng-container>
<ng-container matColumnDef="service">
- <th mat-header-cell *matHeaderCellDef class="service">
+ <th mat-header-cell *matHeaderCellDef class="service label-header">
<div class="label"><span class="text"> Product</span> </div>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -140,7 +140,7 @@
</ng-container>
<ng-container matColumnDef="charge" stickyEnd>
- <th mat-header-cell *matHeaderCellDef class="th_charges">
+ <th mat-header-cell *matHeaderCellDef class="th_charges label-header">
<div class="label">
<div class="sort">
<div class="sort-arrow up" (click)="sortBy('cost', 'down')" [ngClass]="{'active': !!this.active['costdown']}"></div>
@@ -161,45 +161,45 @@
<!-- ----------------FILTER -->
<ng-container matColumnDef="name-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<input #nameFilter type="text" placeholder="Filter by environment name" class="form-control filter-field"
[value]="filtered?.dlab_id" (input)="filteredReportData.dlab_id = $event.target['value']" />
</th>
</ng-container>
<ng-container matColumnDef="user-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)" [type]="'user'"
[items]="filterConfiguration.users" [model]="filteredReportData.users"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="project-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)" [type]="'project'"
[items]="filterConfiguration.projects" [model]="filteredReportData.projects"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="type-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)" [type]="['resource_type']"
[items]="filterConfiguration.resource_type" [model]="filteredReportData.resource_type">
</multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="status-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)" [type]="'status'"
[items]="filterConfiguration.statuses" [model]="filteredReportData.statuses"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="shape-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)"
[type]="'shapes'" [items]="filterConfiguration['shapes']"
[model]="filteredReportData['shapes']"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="service-filter">
- <th mat-header-cell *matHeaderCellDef>
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
<multi-select-dropdown *ngIf="filterConfiguration" (selectionChange)="onUpdate($event)"
[type]="['products']"
[items]="filterConfiguration['products']"
@@ -207,8 +207,8 @@
</th>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
- <th mat-header-cell *matHeaderCellDef>
- <div class="actions">
+ <th mat-header-cell *matHeaderCellDef class="filter-row-item">
+ <div class="actions th_charges">
<button mat-icon-button class="btn reset" (click)="resetFiltering(); isFiltered = !isFiltered">
<i class="material-icons">close</i>
</button>
diff --git a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.scss b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.scss
index 1e8082b..9c4f819 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.scss
@@ -67,6 +67,9 @@
&.header-row {
th {
font-size: 11px;
+ .label{
+ padding-left: 0;
+ }
}
}
}
@@ -116,9 +119,8 @@
}
.th_charges {
- width: 8%;
- min-width: 140px;
- padding-right: 15px;
+ width: 10%;
+ min-width: 155px;
text-align: right;
.label {
@@ -251,7 +253,7 @@
.env_name,
.service,
.th_type,
- .th_rstatus {
+ .th_status {
width: 10%;
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.ts
index 3d99814..ef24283 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting-grid/reporting-grid.component.ts
@@ -18,9 +18,7 @@
*/
import {Component, OnInit, Output, EventEmitter, ViewChild, Input} from '@angular/core';
-
-import { DICTIONARY, ReportingConfigModel } from '../../../dictionary/global.dictionary';
-import {logger} from 'codelyzer/util/logger';
+import { ReportingConfigModel } from '../../../dictionary/global.dictionary';
@Component({
selector: 'dlab-reporting-grid',
@@ -30,7 +28,6 @@
})
export class ReportingGridComponent implements OnInit {
- readonly DICTIONARY = DICTIONARY;
filterConfiguration: ReportingConfigModel;
filteredReportData: ReportingConfigModel = new ReportingConfigModel([], [], [], [], [], '', '', '', []);
@@ -46,6 +43,7 @@
@Output() resetRangePicker: EventEmitter<boolean> = new EventEmitter();
displayedColumns: string[] = ['name', 'user', 'project', 'type', 'status', 'shape', 'service', 'charge'];
displayedFilterColumns: string[] = ['name-filter', 'user-filter', 'project-filter', 'type-filter', 'status-filter', 'shape-filter', 'service-filter', 'actions'];
+ filtered: any;
ngOnInit() {}
@@ -68,10 +66,18 @@
sortBy(sortItem, direction) {
let report: Array<object>;
if (direction === 'down') {
- report = this.reportData.sort((a, b) => (a[sortItem] > b[sortItem]) ? 1 : ((b[sortItem] > a[sortItem]) ? -1 : 0));
+ report = this.reportData.sort((a, b) => {
+ if (a[sortItem] === null) a = '';
+ if (b[sortItem] === null) b = '';
+ return (a[sortItem] > b[sortItem]) ? 1 : ((b[sortItem] > a[sortItem]) ? -1 : 0);
+ });
}
if (direction === 'up') {
- report = this.reportData.sort((a, b) => (a[sortItem] < b[sortItem]) ? 1 : ((b[sortItem] < a[sortItem]) ? -1 : 0));
+ report = this.reportData.sort((a, b) => {
+ if (a[sortItem] === null) a = '';
+ if (b[sortItem] === null) b = '';
+ return (a[sortItem] < b[sortItem]) ? 1 : ((b[sortItem] < a[sortItem]) ? -1 : 0)
+ });
}
this.refreshData(this.fullReport, report);
this.removeSorting();
@@ -105,4 +111,8 @@
this.filterReport.emit(this.filteredReportData);
this.resetRangePicker.emit(true);
}
+
+ shapeSplit(shape){
+ return shape.split(/(?=S)/g)
+ }
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting.component.ts b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting.component.ts
index 3d04931..70de4ab 100644
--- a/services/self-service/src/main/resources/webapp/src/app/reporting/reporting.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/reporting/reporting.component.ts
@@ -20,8 +20,7 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
-
-import {BillingReportService, HealthStatusService} from '../core/services';
+import {ApplicationSecurityService, BillingReportService, HealthStatusService} from '../core/services';
import { ReportingGridComponent } from './reporting-grid/reporting-grid.component';
import { ToolbarComponent } from './toolbar/toolbar.component';
@@ -29,7 +28,6 @@
import { DICTIONARY, ReportingConfigModel } from '../../dictionary/global.dictionary';
import {ProgressBarService} from '../core/services/progress-bar.service';
-
@Component({
selector: 'dlab-reporting',
template: `
@@ -75,10 +73,12 @@
private healthStatusService: HealthStatusService,
public toastr: ToastrService,
private progressBarService: ProgressBarService,
+ private applicationSecurityService: ApplicationSecurityService,
) { }
ngOnInit() {
this.getEnvironmentHealthStatus();
+ this.buildBillingReport();
}
ngOnDestroy() {
@@ -113,17 +113,30 @@
}
rebuildBillingReport(): void {
+ this.checkAutorize();
+ this.buildBillingReport();
+
+ }
+
+ buildBillingReport() {
this.clearStorage();
this.resetRangePicker();
this.reportData.defaultConfigurations();
this.getGeneralBillingData();
}
+ private checkAutorize() {
+ this.applicationSecurityService.isLoggedIn().subscribe( () => {
+ this.getEnvironmentHealthStatus();
+ }
+ );
+ }
+
exportBillingReport(): void {
this.billingReportService.downloadReport(this.reportData)
.subscribe(
data => FileUtils.downloadFile(data),
- error => this.toastr.error('Billing report export failed!', 'Oops!'));
+ () => this.toastr.error('Billing report export failed!', 'Oops!'));
}
getDefaultFilterConfiguration(data): void {
@@ -143,25 +156,22 @@
types.push(item['resource_type']);
if (item.shape && types.indexOf(item.shape)) {
- shapes.push(item.shape);
+ if (item.shape.indexOf('Master') > -1) {
+ for (let shape of item.shape.split(/(?=S)/g)) {
+ shape = shape.replace('Master: ', '');
+ shape = shape.replace(/Slave: /, '');
+ shape = shape.replace(/\s+/g, '');
+ shapes.indexOf(shape) === -1 && shapes.push(shape);
+ }
+ } else if (item.shape.match(/\d x \S+/)) {
+ const parsedShape = item.shape.match(/\d x \S+/)[0].split(' x ')[1];
+ if (shapes.indexOf(parsedShape) === -1) {
+ shapes.push(parsedShape);
+ }
+ } else {
+ shapes.indexOf(item.shape) === -1 && shapes.push(item.shape);
+ }
}
- // if (item.shapes.indexOf('Master') > -1) {
- // for (let shape of item.shapes.split('\n')) {
- // shape = shape.replace('Master: ', '');
- // shape = shape.replace(/Slave:\s+\d+ x /, '');
- // shape = shape.replace(/\s+/g, '');
- //
- // shapes.indexOf(shape) === -1 && shapes.push(shape);
- // }
- // } else if (item.shapes.match(/\d x \S+/)) {
- // const parsedShape = item.shapes.match(/\d x \S+/)[0].split(' x ')[1];
- // if (shapes.indexOf(parsedShape) === -1) {
- // shapes.push(parsedShape);
- // }
- // } else {
- // shapes.indexOf(item.shapes) === -1 && shapes.push(item.shapes);
- // }
- // }
if (item.product && services.indexOf(item.product) === -1)
services.push(item.product);
@@ -199,7 +209,6 @@
.subscribe((result: any) => {
this.billingEnabled = result.billingEnabled;
this.admin = result.admin;
- this.rebuildBillingReport();
});
}
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.html
index 95ae591..1331f5f 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.html
@@ -35,24 +35,24 @@
<mat-list>
<mat-list-item class="list-header">
<div class="resource-name ellipsis" [ngClass]="{ 'wide-name-field' : provider === 'azure' }">Name</div>
- <div class="service">{{ DICTIONARY[provider].service }}</div>
- <div class="resource-type" *ngIf="provider === 'aws'">Type</div>
- <div class="cost-currency">Cost</div>
+ <div class="service">Product</div>
+<!-- <div class="resource-type" *ngIf="provider === 'aws'">Type</div>-->
<div class="usage-date-start">Start</div>
<div class="usage-date-end">End</div>
+ <div class="cost-currency">Cost</div>
</mat-list-item>
<div class="scrolling-content" id="scrolling">
- <mat-list-item *ngFor="let item of notebook.billing">
+ <mat-list-item *ngFor="let item of notebook.billing.report_lines">
<div class="resource-name" [ngClass]="{ 'wide-name-field' : provider === 'azure' }"
- matTooltip="{{ item[DICTIONARY[provider].billing.resourceName] }}"
+ matTooltip="{{ item.resource_name }}"
matTooltipPosition="above">
- {{ item[DICTIONARY[provider].billing.resourceName] }}
+ {{ item.resource_name }}
</div>
- <div class="service">{{ item[DICTIONARY[provider].billing.service] }}</div>
- <div class="resource-type" *ngIf="provider === 'aws'">{{ item[DICTIONARY[provider].billing.type] }}</div>
- <div class="cost-currency">{{ item[DICTIONARY[provider].billing.cost] }} {{ item[DICTIONARY[provider].billing.currencyCode] }}</div>
- <div class="usage-date-start">{{ item[DICTIONARY[provider].billing.dateFrom] | date }}</div>
- <div class="usage-date-end">{{ item[DICTIONARY[provider].billing.dateTo] | date }}</div>
+ <div class="service">{{ item.product }}</div>
+<!-- <div class="resource-type" >{{ item.resourse_type }}</div>-->
+ <div class="usage-date-start">{{ item.from | date }}</div>
+ <div class="usage-date-end">{{ item.to | date }}</div>
+ <div class="cost-currency">{{ item.cost }} {{ item.currency }}</div>
</mat-list-item>
</div>
</mat-list>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.scss
index 64ef43c..18998ea 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/cost-details-dialog/cost-details-dialog.component.scss
@@ -43,7 +43,7 @@
.resource-name,
.usage-date-start,
.usage-date-end {
- width: 15%;
+ width: 20%;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
index 965f9d8..69edcc3 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
@@ -39,7 +39,7 @@
</ng-container> -->
<ng-container matColumnDef="name" sticky>
- <th mat-header-cell *matHeaderCellDef class="name-col">
+ <th mat-header-cell *matHeaderCellDef class="name-col label-header">
<span class="label">Environment name</span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -50,7 +50,7 @@
</th>
</ng-container>
<ng-container matColumnDef="statuses">
- <th mat-header-cell *matHeaderCellDef class="status-col">
+ <th mat-header-cell *matHeaderCellDef class="status-col label-header">
<span class="label"> Status </span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -61,7 +61,7 @@
</th>
</ng-container>
<ng-container matColumnDef="shapes">
- <th mat-header-cell *matHeaderCellDef class="shape-col">
+ <th mat-header-cell *matHeaderCellDef class="shape-col label-header">
<span class="label"> Size </span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -72,12 +72,12 @@
</th>
</ng-container>
<ng-container matColumnDef="tag">
- <th mat-header-cell *matHeaderCellDef class="tag-col">
+ <th mat-header-cell *matHeaderCellDef class="tag-col label-header">
<span class="label"> Tags </span>
</th>
</ng-container>
<ng-container matColumnDef="resources">
- <th mat-header-cell *matHeaderCellDef class="resources-col">
+ <th mat-header-cell *matHeaderCellDef class="resources-col label-header">
<span class="label"> Computational resources </span>
<button mat-icon-button aria-label="More" class="ar" (click)="toggleFilterRow()">
<i class="material-icons">
@@ -88,12 +88,12 @@
</th>
</ng-container>
<ng-container matColumnDef="cost">
- <th mat-header-cell *matHeaderCellDef class="cost-col">
+ <th mat-header-cell *matHeaderCellDef class="cost-col label-header">
<span class="label"> Cost </span>
</th>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
- <th mat-header-cell *matHeaderCellDef class="actions-col">
+ <th mat-header-cell *matHeaderCellDef class="actions-col label-header">
<span class="label"> Actions </span>
</th>
</ng-container>
@@ -139,7 +139,7 @@
<td *ngIf="healthStatus?.billingEnabled" class="cost-col">
<span class="total_cost">{{ element.cost || 'N/A' }} {{ element.currency_code || '' }}</span>
<span (click)="element.billing && printCostDetails(element)" class="currency_details"
- [ngClass]="{ 'not-allowed' : !element.billing }">
+ [ngClass]="{ 'not-allowed' : !element.billing.report_lines.length }">
<i class="material-icons">help_outline</i>
</span>
</td>
@@ -168,10 +168,12 @@
</div>
</li>
<li *ngIf="element.status.toLowerCase() === 'stopped' || element.status.toLowerCase() === 'stopping'"
- matTooltip="{{isEdgeNodeStopped(element) ? 'Unable to run notebook if edge node is stopped.' : 'Unable to run notebook until it will be stopped.'}}" matTooltipPosition="above"
- [matTooltipDisabled]="!isResourcesInProgress(element) && element.status.toLowerCase() !== 'stopping' && !isEdgeNodeStopped(element)">
+ matTooltip="{{element.edgeNodeStatus !== 'running' ? 'Unable to run notebook if edge node is not running.' : 'Unable to run notebook until it will be stopped.'}}" matTooltipPosition="above"
+ [matTooltipDisabled]="!isResourcesInProgress(element) && element.status.toLowerCase() !== 'stopping' && element.edgeNodeStatus === 'running'"
+ [ngClass]="{'not-allow': isResourcesInProgress(element) || element.status.toLowerCase() === 'stopping' || element.edgeNodeStatus !== 'running' }"
+ >
<div (click)="exploratoryAction(element, 'run')"
- [ngClass]="{'not-allowed': isResourcesInProgress(element) || element.status.toLowerCase() === 'stopping' || isEdgeNodeStopped(element) }">
+ [ngClass]="{'not-allowed': isResourcesInProgress(element) || element.status.toLowerCase() === 'stopping' || element.edgeNodeStatus !== 'running' }">
<i class="material-icons">play_circle_outline</i>
<span>Run</span>
</div>
@@ -222,37 +224,37 @@
<!-- FILTER START -->
<ng-container matColumnDef="name-filter" sticky>
- <th mat-header-cell *matHeaderCellDef class="name-col">
+ <th mat-header-cell *matHeaderCellDef class="name-col filter-row-item">
<input placeholder="Filter by environment name" type="text" class="form-control filter-field"
- [value]="filterForm.name" (input)="filterForm.name = $event.target.value" />
+ [value]="filterForm.name" (input)="filterForm.name = $event.target['value']" />
</th>
</ng-container>
<ng-container matColumnDef="status-filter">
- <th mat-header-cell *matHeaderCellDef class="status-col">
+ <th mat-header-cell *matHeaderCellDef class="status-col filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'statuses'"
[items]="filterConfiguration.statuses" [model]="filterForm.statuses"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="shape-filter">
- <th mat-header-cell *matHeaderCellDef class="shape-col">
+ <th mat-header-cell *matHeaderCellDef class="shape-col filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)"
[type]="'sizes'" [items]="filterConfiguration.shapes"
[model]="filterForm.shapes"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="tag-filter">
- <th mat-header-cell *matHeaderCellDef class="tag-col">
+ <th mat-header-cell *matHeaderCellDef class="tag-col filter-row-item">
</th>
</ng-container>
<ng-container matColumnDef="resource-filter">
- <th mat-header-cell *matHeaderCellDef class="resources-col">
+ <th mat-header-cell *matHeaderCellDef class="resources-col filter-row-item">
<multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'resources'"
[items]="filterConfiguration.resources" [model]="filterForm.resources"></multi-select-dropdown>
</th>
</ng-container>
<ng-container matColumnDef="cost-filter">
- <th mat-header-cell *matHeaderCellDef class="cost-col">
+ <th mat-header-cell *matHeaderCellDef class="cost-col filter-row-item">
</th>
</ng-container>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
index e09ff3e..d6c0556 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.scss
@@ -395,3 +395,7 @@
.content-row{
background-clip: padding-box;
}
+
+.not-allow{
+ cursor: not-allowed !important;
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
index 3052d84..8dfdf4e 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
@@ -151,12 +151,6 @@
return false;
}
- public isEdgeNodeStopped(resource) {
- const currProject = this.projects.filter(proj => proj.name === resource.project);
- const currEdgenodeStatus = currProject[0].endpoints.filter(node => node.name === resource.endpoint)[0].status;
- return currEdgenodeStatus === 'STOPPED' || currEdgenodeStatus === 'STOPPING';
- }
-
public filterActiveInstances(): FilterConfigurationModel {
return (<FilterConfigurationModel | any>Object).assign({}, this.filterConfiguration, {
statuses: SortUtils.activeStatuses(),
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
index 14a2824..e769dbe 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts
@@ -50,6 +50,7 @@
public project: string,
public endpoint: string,
public tags: any,
+ public edgeNodeStatus: string
) { }
public static loadEnvironments(data: Array<any>) {
@@ -59,36 +60,39 @@
project: value.project,
exploratory: value.exploratory.map(el => {
const provider = el.cloud_provider.toLowerCase();
+ const billing = value.exploratoryBilling.filter(res => res.name === el.exploratory_name)[0];
return new ExploratoryModel(
- provider,
- el.exploratory_name,
- el.template_name,
- el.image,
- el.status,
- el.shape,
- el.computational_resources,
- el.up_time,
- el.exploratory_url,
- value.shared[el.endpoint].edge_node_ip,
- el.private_ip,
- el.exploratory_user,
- el.exploratory_pass,
- value.shared[el.endpoint][DICTIONARY[provider].bucket_name],
- value.shared[el.endpoint][DICTIONARY[provider].shared_bucket_name],
- el.error_message,
- el[DICTIONARY[provider].billing.cost],
- el[DICTIONARY[provider].billing.currencyCode],
- el.billing,
- el.libs,
- value.shared[el.endpoint][DICTIONARY[provider].user_storage_account_name],
- value.shared[el.endpoint][DICTIONARY[provider].shared_storage_account_name],
- value.shared[el.endpoint][DICTIONARY[provider].datalake_name],
- value.shared[el.endpoint][DICTIONARY[provider].datalake_user_directory_name],
- value.shared[el.endpoint][DICTIONARY[provider].datalake_shared_directory_name],
- el.project,
- el.endpoint,
- el.tags,
- )})
+ provider,
+ el.exploratory_name,
+ el.template_name,
+ el.image,
+ el.status,
+ el.shape,
+ el.computational_resources,
+ el.up_time,
+ el.exploratory_url,
+ value.shared[el.endpoint].edge_node_ip,
+ el.private_ip,
+ el.exploratory_user,
+ el.exploratory_pass,
+ value.shared[el.endpoint][DICTIONARY[provider].bucket_name],
+ value.shared[el.endpoint][DICTIONARY[provider].shared_bucket_name],
+ el.error_message,
+ billing ? billing.total_cost : '',
+ billing ? billing.currency : '',
+ billing,
+ el.libs,
+ value.shared[el.endpoint][DICTIONARY[provider].user_storage_account_name],
+ value.shared[el.endpoint][DICTIONARY[provider].shared_storage_account_name],
+ value.shared[el.endpoint][DICTIONARY[provider].datalake_name],
+ value.shared[el.endpoint][DICTIONARY[provider].datalake_user_directory_name],
+ value.shared[el.endpoint][DICTIONARY[provider].datalake_shared_directory_name],
+ el.project,
+ el.endpoint,
+ el.tags,
+ value.shared[el.endpoint].status
+ );
+ })
};
});
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
index 091ccb7..b705c38 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html
@@ -63,5 +63,5 @@
</div>
</div>
<mat-divider></mat-divider>
- <resources-grid [projects] = "projects"></resources-grid>
+ <resources-grid></resources-grid>
</div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
index bab05a7..2587a62 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts
@@ -17,14 +17,14 @@
* under the License.
*/
-import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { MatDialog } from '@angular/material/dialog';
import { ResourcesGridComponent } from './resources-grid/resources-grid.component';
import { ExploratoryEnvironmentCreateComponent } from './exploratory/create-environment';
import { Exploratory } from './resources-grid/resources-grid.model';
-import { HealthStatusService, ProjectService } from '../core/services';
+import {ApplicationSecurityService, HealthStatusService} from '../core/services';
import { ManageUngitComponent } from './manage-ungit/manage-ungit.component';
import { Project } from './../administration/project/project.component';
@@ -45,12 +45,11 @@
public toastr: ToastrService,
private healthStatusService: HealthStatusService,
private dialog: MatDialog,
- private projectService: ProjectService
+ private applicationSecurityService: ApplicationSecurityService
) { }
ngOnInit() {
this.getEnvironmentHealthStatus();
- this.getProjects();
this.exploratoryEnvironments = this.resourcesGrid.environments;
}
@@ -61,8 +60,7 @@
public refreshGrid(): void {
this.resourcesGrid.buildGrid();
- this.getProjects();
- this.getEnvironmentHealthStatus();
+ this.checkAutorize();
this.exploratoryEnvironments = this.resourcesGrid.environments;
}
@@ -89,8 +87,11 @@
return this.resourcesGrid.activeProject;
}
- private getProjects() {
- this.projectService.getUserProjectsList().subscribe((projects: any) => this.projects = projects);
+ private checkAutorize() {
+ this.applicationSecurityService.isLoggedIn().subscribe( () => {
+ this.getEnvironmentHealthStatus();
+ }
+ );
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html
index 4e41606..420aa09 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.html
@@ -38,9 +38,11 @@
</li>
<ng-template ngFor let-item [ngForOf]="items" let-i="index">
- <li class="role-label" role="presentation" *ngIf="i === 0 || model && item.type !== items[i - 1].type" (click)="toggleItemsForLable(item.type, $event)">
+ <li class="role-label" role="presentation" *ngIf="i === 0 || model && item.type !== items[i - 1].type" (click)="toggleItemsForLable(item.type, $event)" >
<a href="#" class="list-item" role="menuitem">
- <span class="arrow" [ngClass]="{'rotate-arrow': isOpenCategory[item.type], 'arrow-checked': selectedAllInCattegory(item.type) || selectedSomeInCattegory(item.type)}"></span>
+ <span class="arrow" [ngClass]="{'rotate-arrow': isOpenCategory[item.type], 'arrow-checked': selectedAllInCattegory(item.type) || selectedSomeInCattegory(item.type)}">
+ <i class="material-icons">keyboard_arrow_right</i>
+ </span>
<span class="empty-checkbox" [ngClass]="{'checked': selectedAllInCattegory(item.type) || selectedSomeInCattegory(item.type)}" (click)="toggleselectedCategory($event, model, item.type);$event.stopPropagation()" >
<span class="checked-checkbox" *ngIf="selectedAllInCattegory(item.type)"></span>
<span class="line-checkbox" *ngIf="selectedSomeInCattegory(item.type)"></span>
@@ -49,7 +51,11 @@
</a>
</li>
- <li class="role-item" role="presentation" *ngIf="model && isOpenCategory[item.type] && item.type !== 'COMPUTATIONAL_SHAPE' && item.type !== 'NOTEBOOK_SHAPE'" >
+ <li class="role-item"
+ role="presentation"
+ *ngIf="model && isOpenCategory[item.type] && item.type !== 'COMPUTATIONAL_SHAPE' && item.type !== 'NOTEBOOK_SHAPE'"
+ [hidden]="!isAdmin && item.role === 'Allow to execute administration operation'"
+ >
<a href="#" class="list-item" role="menuitem" (click)="toggleSelectedOptions($event, model, item)">
<span class="empty-checkbox" [ngClass]="{'checked': checkInModel(item.role)}">
<span class="checked-checkbox" *ngIf="checkInModel(item.role)"></span>
@@ -64,12 +70,20 @@
|| model && isOpenCategory[item.type] && item.type === 'COMPUTATIONAL_SHAPE' && item.type !== items[i - 1].type"
>
<a href="#" class="list-item" role="menuitem">
- <span class="arrow" [ngClass]="{'rotate-arrow': isCloudOpen[item.type + item.cloud], 'arrow-checked': selectedAllInCloud(item.type, item.cloud) || selectedSomeInCloud(item.type, item.cloud)}"></span>
- <span class="empty-checkbox" [ngClass]="{'checked': selectedAllInCloud(item.type, item.cloud) || selectedSomeInCloud(item.type, item.cloud)}" (click)="toggleSelectedCloud($event, model, item.type, item.cloud);$event.stopPropagation()" >
+ <span class="arrow" [ngClass]="{'rotate-arrow': isCloudOpen[item.type + item.cloud], 'arrow-checked': selectedAllInCloud(item.type, item.cloud) || selectedSomeInCloud(item.type, item.cloud)}">
+ <i class="material-icons">keyboard_arrow_right</i>
+ </span>
+ <span class="empty-checkbox"
+ [ngClass]="{
+ 'checked': selectedAllInCloud(item.type, item.cloud)
+ || selectedSomeInCloud(item.type, item.cloud)}"
+ (click)="toggleSelectedCloud($event, model, item.type, item.cloud);
+ $event.stopPropagation()"
+ >
<span class="checked-checkbox" *ngIf="selectedAllInCloud(item.type, item.cloud)"></span>
<span class="line-checkbox" *ngIf="selectedSomeInCloud(item.type, item.cloud)"></span>
</span>
- {{item.cloud || 'AWS'}}
+ {{item.cloud}}
</a>
</li>
<li class="role-cloud-item" role="presentation" *ngIf="model && isCloudOpen[item.type + item.cloud] && isOpenCategory[item.type]" >
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
index 5323a24..a066dd5 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
@@ -278,23 +278,22 @@
}
&.arrow{
- width: 16px;
- height: 14px;
- border: 8px solid transparent;
- border-left: 8px solid lightgrey;
- left: 10px;
- top: 12px;
- border-radius: 3px;
-
+ left: 2px;
+ top: 9px;
+ i{
+ color: lightgrey;
+ }
&.rotate-arrow{
transform: rotate(90deg);
transition: .1s ease-in-out;
- top: 15px;
- left: 6px;
+ top: 6px;
+ left: 0;
}
&.arrow-checked{
- border-left: 8px solid #35afd5;
+ i{
+ color: #36afd5
+ }
}
}
}
@@ -319,3 +318,7 @@
}
}
+.d-none{
+ display: none;
+}
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts
index cabf7d9..5b9c1a9 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.ts
@@ -30,6 +30,7 @@
@Input() items: Array<any>;
@Input() model: Array<any>;
@Input() type: string;
+ @Input() isAdmin: boolean;
@Output() selectionChange: EventEmitter<{}> = new EventEmitter();
public isOpenCategory = {
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.model.ts b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.model.ts
index 1bfcd06..79b0512 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.model.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/confirmation-dialog/confirmation-dialog.model.ts
@@ -76,9 +76,9 @@
}
private terminateExploratory(): Observable<{}> {
- return this.manageAction
- ? this.manageEnvironmentsService.environmentManagement(this.notebook.user, 'terminate', this.notebook.project, this.notebook.name)
- : this.userResourceService.suspendExploratoryEnvironment(this.notebook, 'terminate');
+ return this.manageAction ? this.manageEnvironmentsService.environmentManagement(
+ this.notebook.user, 'terminate', this.notebook.project, this.notebook.name
+ ) : this.userResourceService.suspendExploratoryEnvironment(this.notebook, 'terminate');
}
private stopEdgeNode(): Observable<{}> {
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
index 952dfd0..1f33caa 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts
@@ -111,7 +111,7 @@
this.subscriptions.add(this.healthStatusService.statusData.pipe(skip(1)).subscribe(result => {
this.healthStatus = result;
result.status && this.checkQuoteUsed(this.healthStatus);
- result.status && !result.projectAssigned && this.checkAssignment(this.healthStatus);
+ result.status && !result.projectAssigned && !result.admin && this.checkAssignment(this.healthStatus);
}));
this.subscriptions.add(timer(0, this.CHECK_ACTIVE_SCHEDULE_TIMEOUT).subscribe(() => this.refreshSchedulerData()));
this.currentUserName = this.getUserName();
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
index 7b48bba..43e9c50 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
@@ -662,3 +662,8 @@
}
}
+.filter-row-item, .label-header{
+ box-shadow: inset 0 -1px 0 lightgrey;
+ border-bottom: none !important;
+}
+
diff --git a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ImageExploratoryResourceTest.java b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ImageExploratoryResourceTest.java
index 38c0e46..d74f94e 100644
--- a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ImageExploratoryResourceTest.java
+++ b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ImageExploratoryResourceTest.java
@@ -272,7 +272,7 @@
}
private List<ImageInfoRecord> getImageList() {
- ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someApp",
+ ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp",
"someFullName", ImageStatus.CREATED);
return Collections.singletonList(imageInfoRecord);
}
diff --git a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/UserRoleResourceTest.java b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/UserRoleResourceTest.java
index c335db7..c4e2bd6 100644
--- a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/UserRoleResourceTest.java
+++ b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/UserRoleResourceTest.java
@@ -62,7 +62,7 @@
@Test
public void getRoles() {
- when(rolesService.getUserRoles(getUserInfo())).thenReturn(Collections.singletonList(getUserRole()));
+ when(rolesService.getUserRoles()).thenReturn(Collections.singletonList(getUserRole()));
final Response response = resources.getJerseyTest()
.target("/role")
@@ -77,7 +77,7 @@
assertEquals(ROLE_ID, actualRoles.get(0).getId());
assertEquals(MediaType.APPLICATION_JSON, response.getHeaderString(HttpHeaders.CONTENT_TYPE));
- verify(rolesService).getUserRoles(getUserInfo());
+ verify(rolesService).getUserRoles();
verifyNoMoreInteractions(rolesService);
}
diff --git a/services/self-service/src/test/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImplTest.java b/services/self-service/src/test/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
index 57d0284..e15044b 100644
--- a/services/self-service/src/test/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/dlab/backendapi/service/impl/ImageExploratoryServiceImplTest.java
@@ -24,8 +24,10 @@
import com.epam.dlab.backendapi.dao.ExploratoryLibDAO;
import com.epam.dlab.backendapi.dao.ImageExploratoryDao;
import com.epam.dlab.backendapi.domain.EndpointDTO;
+import com.epam.dlab.backendapi.domain.ProjectDTO;
import com.epam.dlab.backendapi.resources.dto.ImageInfoRecord;
import com.epam.dlab.backendapi.service.EndpointService;
+import com.epam.dlab.backendapi.service.ProjectService;
import com.epam.dlab.backendapi.util.RequestBuilder;
import com.epam.dlab.cloud.CloudProvider;
import com.epam.dlab.dto.UserInstanceDTO;
@@ -91,6 +93,8 @@
private RequestBuilder requestBuilder;
@Mock
private EndpointService endpointService;
+ @Mock
+ private ProjectService projectService;
@InjectMocks
private ImageExploratoryServiceImpl imageExploratoryService;
@@ -107,6 +111,7 @@
@Test
public void createImage() {
+ when(projectService.get(anyString())).thenReturn(getProjectDTO());
when(exploratoryDAO.fetchRunningExploratoryFields(anyString(), anyString(), anyString())).thenReturn(userInstance);
when(imageExploratoryDao.exist(anyString(), anyString())).thenReturn(false);
@@ -117,7 +122,7 @@
ExploratoryImageDTO eiDto = new ExploratoryImageDTO();
when(endpointService.get(anyString())).thenReturn(endpointDTO());
when(requestBuilder.newExploratoryImageCreate(any(UserInfo.class), any(UserInstanceDTO.class), anyString(),
- any(EndpointDTO.class))).thenReturn(eiDto);
+ any(EndpointDTO.class), any(ProjectDTO.class))).thenReturn(eiDto);
String expectedUuid = "someUuid";
when(provisioningService.post(anyString(), anyString(), any(ExploratoryImageDTO.class), any()))
@@ -129,15 +134,16 @@
assertNotNull(actualUuid);
assertEquals(expectedUuid, actualUuid);
+ verify(projectService).get(PROJECT);
verify(exploratoryDAO).fetchRunningExploratoryFields(USER, PROJECT, EXPLORATORY_NAME);
verify(exploratoryDAO).updateExploratoryStatus(any(ExploratoryStatusDTO.class));
verify(imageExploratoryDao).exist(imageName, PROJECT);
verify(imageExploratoryDao).save(any(Image.class));
verify(libDAO).getLibraries(USER, PROJECT, EXPLORATORY_NAME);
- verify(requestBuilder).newExploratoryImageCreate(userInfo, userInstance, imageName, endpointDTO());
+ verify(requestBuilder).newExploratoryImageCreate(userInfo, userInstance, imageName, endpointDTO(), getProjectDTO());
verify(endpointService).get(anyString());
verify(provisioningService).post(endpointDTO().getUrl() + "exploratory/image", TOKEN, eiDto, String.class);
- verifyNoMoreInteractions(exploratoryDAO, imageExploratoryDao, libDAO, requestBuilder, endpointService, provisioningService);
+ verifyNoMoreInteractions(projectService, exploratoryDAO, imageExploratoryDao, libDAO, requestBuilder, endpointService, provisioningService);
}
@Test
@@ -170,6 +176,7 @@
@Test
public void createImageWhenMethodNewExploratoryImageCreateThrowsException() {
+ when(projectService.get(anyString())).thenReturn(getProjectDTO());
when(exploratoryDAO.fetchRunningExploratoryFields(anyString(), anyString(), anyString())).thenReturn(userInstance);
when(imageExploratoryDao.exist(anyString(), anyString())).thenReturn(false);
@@ -178,7 +185,7 @@
when(exploratoryDAO.updateExploratoryStatus(any(ExploratoryStatusDTO.class)))
.thenReturn(mock(UpdateResult.class));
doThrow(new DlabException("Cannot create instance of resource class")).when(requestBuilder)
- .newExploratoryImageCreate(any(UserInfo.class), any(UserInstanceDTO.class), anyString(), any(EndpointDTO.class));
+ .newExploratoryImageCreate(any(UserInfo.class), any(UserInstanceDTO.class), anyString(), any(EndpointDTO.class), any(ProjectDTO.class));
when(endpointService.get(anyString())).thenReturn(endpointDTO());
String imageName = "someImageName", imageDescription = "someDescription";
@@ -188,14 +195,15 @@
assertEquals("Cannot create instance of resource class", e.getMessage());
}
+ verify(projectService).get(PROJECT);
verify(exploratoryDAO).fetchRunningExploratoryFields(USER, PROJECT, EXPLORATORY_NAME);
verify(exploratoryDAO).updateExploratoryStatus(any(ExploratoryStatusDTO.class));
verify(imageExploratoryDao).exist(imageName, PROJECT);
verify(imageExploratoryDao).save(any(Image.class));
verify(libDAO).getLibraries(USER, PROJECT, EXPLORATORY_NAME);
- verify(requestBuilder).newExploratoryImageCreate(userInfo, userInstance, imageName, endpointDTO());
+ verify(requestBuilder).newExploratoryImageCreate(userInfo, userInstance, imageName, endpointDTO(), getProjectDTO());
verify(endpointService).get(anyString());
- verifyNoMoreInteractions(exploratoryDAO, imageExploratoryDao, libDAO, requestBuilder, endpointService);
+ verifyNoMoreInteractions(projectService, exploratoryDAO, imageExploratoryDao, libDAO, requestBuilder, endpointService);
}
@Test
@@ -300,7 +308,7 @@
}
private ImageInfoRecord getImageInfoRecord() {
- return new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someApp",
+ return new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp",
"someFullName", ImageStatus.CREATED);
}
@@ -337,4 +345,8 @@
private EndpointDTO endpointDTO() {
return new EndpointDTO("test", "url", "", null, EndpointDTO.EndpointStatus.ACTIVE, CloudProvider.AWS);
}
+
+ private ProjectDTO getProjectDTO() {
+ return ProjectDTO.builder().name(PROJECT).build();
+ }
}