diff --git a/.travis.yml b/.travis.yml index ac659114..1723c6bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ python: script: - pip install -r ./bin/requirements.txt - pytest - - cd flink_jobs/ams_ingest_metric/ && mvn test - - cd ../batch_ar && mvn test - - cd ../batch_status && mvn test - - cd ../stream_status && mvn test - - cd ../ams_ingest_sync && mvn test + - cd flink_jobs/ams_ingest_metric/ && travis_wait mvn test + - cd ../batch_ar && travis_wait mvn test + - cd ../batch_status && travis_wait mvn test + - cd ../stream_status && travis_wait mvn test + - cd ../ams_ingest_sync && travis_wait mvn test diff --git a/README.md b/README.md index bccafe80..8ca543e1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ Job optional cli parameters: `--ams.verify` : optional turn on/off ssl verify +### Restart strategy +Job has a fixed delay restart strategy. If it fails it will try to restart for a maximum of 10 attempt with a retry interval of 2 minutes +between each attempt + ### Metric data hbase schema Metric data are stored in hbase tables using different namespaces for different tenants (e.g. hbase table name = '{TENANT_name}:metric_data') @@ -127,6 +131,9 @@ Job required cli parameters: `--ams.verify` : optional turn on/off ssl verify +### Restart strategy +Job has a fixed delay restart strategy. If it fails it will try to restart for a maximum of 10 attempt with a retry interval of 2 minutes +between each attempt ### Stream Status @@ -210,6 +217,9 @@ Other optional cli parameters `--ams.verify` : optional turn on/off ssl verify +### Restart strategy +Job has a fixed delay restart strategy. If it fails it will try to restart for a maximum of 10 attempt with a retry interval of 2 minutes +between each attempt ### Status events schema @@ -233,6 +243,25 @@ Status events are generated as JSON messages that are defined by the following c A metric data message can produce zero, one or more status metric events. The system analyzes the new status introduced by the metric and then aggregates on top levels to see if any other status changes are produced. If a status of an item actually changes an appropriate status event is produced based on the item type (endpoint_group,service,endpoint,metric). +## Threshold rule files +Each report can be accompanied by a threshold rules file which includes rules on low level metric data which may accompany a monitoring message with the field 'actual_data'. +The rule file is in JSON format and has the following schema: +``` +{ + "rules": [ + { + "group" : "site-101", + "host" : "host.foo", + "metric": "org.namespace.metric", + "thresholds": "firstlabel=10s;30;50:60;0;100 secondlabel=5;0:10;20:30;50;30" + } + ] +} +``` +Each rule has multiple thresholds separated by whitespace. Each threshold has the following format: +`firstlabel=10s;30;50:60;0;100` which corresponds to `{{label}}={{value}}{{uom}};{{warning-range}};{{critical-range}};{{min}};{{max}}`. Each range is in the form of`{{floor}}:{{ceiling}}` but some shortcuts can be taken in declarations. + + ## Batch Status Flink batch job that calculates status results for a specific date @@ -273,6 +302,8 @@ Job required cli parameters: `--mongo.method` : MongoDB method to be used when storing the results ~ either: `insert` or `upsert` +`--thr` : (optional) file location of threshold rules + ## Batch AR @@ -318,6 +349,8 @@ Job required cli parameters: `--mongo.method` : MongoDB method to be used when storing the results ~ either: `insert` or `upsert` +`--thr` : (optional) file location of threshold rules + ## Flink job names Running flink jobs can be listed either in flink dashboard by visiting `http://{{flink.webui.host}}:{{flink.webui.port}}` diff --git a/bin/ar_job_submit.py b/bin/ar_job_submit.py index 9d76cf3f..005ef0a0 100755 --- a/bin/ar_job_submit.py +++ b/bin/ar_job_submit.py @@ -5,88 +5,101 @@ import argparse import datetime from snakebite.client import Client -import ConfigParser import logging from urlparse import urlparse -from utils.argo_log import ArgoLogger from utils.argo_mongo import ArgoMongoClient -from utils.common import cmd_toString, date_rollback, flink_job_submit, hdfs_check_path +from utils.common import cmd_to_string, date_rollback, flink_job_submit, hdfs_check_path, get_log_conf, get_config_paths from utils.update_profiles import ArgoProfileManager +from utils.argo_config import ArgoConfig +from utils.recomputations import upload_recomputations -def compose_hdfs_commands(year, month, day, args, config, logger): + +log = logging.getLogger(__name__) + + +def compose_hdfs_commands(year, month, day, args, config): # set up the hdfs client to be used in order to check the files - client = Client(config.get("HDFS", "hdfs_host"), config.getint("HDFS", "hdfs_port"), use_trash=False) + namenode = config.get("HDFS", "namenode") + client = Client(namenode.hostname, namenode.port, use_trash=False) # hdfs sync path for the tenant - hdfs_sync = config.get("HDFS", "hdfs_sync") - hdfs_sync = hdfs_sync.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_sync = hdfs_sync.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_sync = hdfs_sync.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_sync = hdfs_sync.replace("{{tenant}}", args.Tenant) - - # hdfs metric path for the tenant - hdfs_metric = config.get("HDFS", "hdfs_metric") - hdfs_metric = hdfs_metric.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_metric = hdfs_metric.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_metric = hdfs_metric.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_metric = hdfs_metric.replace("{{tenant}}", args.Tenant) + + hdfs_user = config.get("HDFS", "user") + tenant = args.tenant + hdfs_sync = config.get("HDFS", "path_sync") + hdfs_sync = hdfs_sync.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() + + hdfs_metric = config.get("HDFS", "path_metric") + + hdfs_metric = hdfs_metric.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() # dictionary holding all the commands with their respective arguments' name - hdfs_commands = {} + hdfs_commands = dict() # file location of previous day's metric data (local or hdfs) - hdfs_commands["--pdata"] = hdfs_check_path(hdfs_metric+"/"+str(datetime.date(year, month, day) - datetime.timedelta(1)), logger, client) + hdfs_commands["--pdata"] = hdfs_check_path( + hdfs_metric + "/" + str(datetime.date(year, month, day) - datetime.timedelta(1)), client) # file location of target day's metric data (local or hdfs) - hdfs_commands["--mdata"] = hdfs_check_path(hdfs_metric+"/"+args.Date, logger, client) + hdfs_commands["--mdata"] = hdfs_check_path(hdfs_metric + "/" + args.date, client) # file location of report configuration json file (local or hdfs) - hdfs_commands["--conf"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_"+args.Report+"_cfg.json", logger, client) + hdfs_commands["--conf"] = hdfs_check_path(hdfs_sync + "/" + args.tenant+"_"+args.report+"_cfg.json", client) # file location of metric profile (local or hdfs) - hdfs_commands["--mps"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"metric_profile_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["--mps"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "metric_profile_" + "{{date}}" + ".avro", year, month, day, config, + client) # file location of operations profile (local or hdfs) - hdfs_commands["--ops"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_ops.json", logger, client) + hdfs_commands["--ops"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_ops.json", client) # file location of aggregations profile (local or hdfs) - hdfs_commands["--apr"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_"+args.Report+"_ap.json", logger, client) + hdfs_commands["--apr"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_"+args.report+"_ap.json", client) + + if args.thresholds: + # file location of thresholds rules file (local or hdfs) + hdfs_commands["--thr"] = hdfs_check_path( + os.path.join(hdfs_sync, "".join([args.tenant, "_", args.report, "_thresholds.json"])), client) # file location of endpoint group topology file (local or hdfs) - hdfs_commands["-egp"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"group_endpoints_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["-egp"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "group_endpoints_" + "{{date}}" + ".avro", year, month, day, config, + client) # file location of group of groups topology file (local or hdfs) - hdfs_commands["-ggp"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"group_groups_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["-ggp"] = date_rollback(hdfs_sync + "/" + args.report + "/" + "group_groups_" + "{{date}}" + ".avro", + year, month, day, config, client) # file location of weights file (local or hdfs) - hdfs_commands["--weights"] = date_rollback(hdfs_sync+"/"+args.Report+"/weights_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["--weights"] = date_rollback(hdfs_sync + "/" + args.report + "/weights_" + "{{date}}" + ".avro", year, + month, day, config, client) # file location of downtimes file (local or hdfs) - hdfs_commands["--downtimes"] = hdfs_check_path(hdfs_sync+"/"+args.Report+"/downtimes_"+str(datetime.date(year, month, day))+".avro", logger, client) + hdfs_commands["--downtimes"] = hdfs_check_path( + hdfs_sync + "/" + args.report + "/downtimes_" + str(datetime.date(year, month, day)) + ".avro", client) # file location of recomputations file (local or hdfs) # first check if there is a recomputations file for the given date - if client.test(urlparse(hdfs_sync+"/recomp_"+args.Date+".json").path, exists=True): - hdfs_commands["--rec"] = hdfs_sync+"/recomp_"+args.Date+".json" + # recomputation lies in the hdfs in the form of + # /sync/recomp_TENANTNAME_ReportName_2018-08-02.json + if client.test(urlparse(hdfs_sync+"/recomp_"+args.tenant+"_"+args.report+"_"+args.date+".json").path, exists=True): + hdfs_commands["--rec"] = hdfs_sync+"/recomp_"+args.date+".json" else: - hdfs_commands["--rec"] = hdfs_check_path(hdfs_sync+"/recomp.json", logger, client) + hdfs_commands["--rec"] = hdfs_check_path(hdfs_sync+"/recomp.json", client) return hdfs_commands -def compose_command(config, args, hdfs_commands, logger=None): +def compose_command(config, args, hdfs_commands): - # job sumbission command + # job submission command cmd_command = [] - if args.Sudo is True: + if args.sudo is True: cmd_command.append("sudo") - # create a simple stream_handler whenever tetsing - if logger is None: - logger = ArgoLogger() - # flink executable cmd_command.append(config.get("FLINK", "path")) @@ -102,105 +115,105 @@ def compose_command(config, args, hdfs_commands, logger=None): # date the report will run for cmd_command.append("--run.date") - cmd_command.append(args.Date) + cmd_command.append(args.date) # MongoDB uri for outputting the results to (e.g. mongodb://localhost:21017/example_db) cmd_command.append("--mongo.uri") - mongo_tenant = "TENANTS:"+args.Tenant+":MONGO" - mongo_uri = config.get(mongo_tenant, "mongo_uri") - mongo_uri = mongo_uri.replace("{{mongo_host}}", config.get(mongo_tenant, "mongo_host")) - mongo_uri = mongo_uri.replace("{{mongo_port}}", config.get(mongo_tenant, "mongo_port")) - cmd_command.append(mongo_uri) - - if args.Method == "insert": - argo_mongo_client = ArgoMongoClient(args, config, logger, ["service_ar", "endpoint_group_ar"]) + group_tenant = "TENANTS:"+args.tenant + mongo_endpoint = config.get("MONGO","endpoint").geturl() + mongo_uri = config.get(group_tenant, "mongo_uri").fill(mongo_endpoint=mongo_endpoint, tenant=args.tenant) + cmd_command.append(mongo_uri.geturl()) + + if args.method == "insert": + argo_mongo_client = ArgoMongoClient(args, config, ["service_ar", "endpoint_group_ar"]) argo_mongo_client.mongo_clean_ar(mongo_uri) # MongoDB method to be used when storing the results, either insert or upsert cmd_command.append("--mongo.method") - cmd_command.append(args.Method) + cmd_command.append(args.method) # add the hdfs commands for command in hdfs_commands: cmd_command.append(command) cmd_command.append(hdfs_commands[command]) - # ams proxy - if config.getboolean("AMS", "proxy_enabled"): + # get optional ams proxy + proxy = config.get("AMS", "proxy") + if proxy is not None: cmd_command.append("--ams.proxy") - cmd_command.append(config.get("AMS", "ams_proxy")) + cmd_command.append(proxy.geturl()) # ssl verify cmd_command.append("--ams.verify") - if config.getboolean("AMS", "ssl_enabled"): - cmd_command.append("true") + ams_verify = config.get("AMS", "verify") + if ams_verify is not None: + cmd_command.append(str(ams_verify).lower()) else: - cmd_command.append("false") + # by default assume ams verify is always true + cmd_command.append("true") return cmd_command def main(args=None): - # make sure the argument are in the correct form - args.Tenant = args.Tenant.upper() - args.Method = args.Method.lower() - - year, month, day = [int(x) for x in args.Date.split("-")] + # Get configuration paths + conf_paths = get_config_paths(args.config) - # set up the config parser - config = ConfigParser.ConfigParser() + # Get logger config file + get_log_conf(conf_paths['log']) - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../conf/conf.cfg") - else: - config.read(args.ConfigPath) + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) - # set up the logger - logger = ArgoLogger(log_name="batch-ar", config=config) + year, month, day = [int(x) for x in args.date.split("-")] # check if configuration for the given tenant exists - if not config.has_section("TENANTS:"+args.Tenant): - logger.print_and_log(logging.CRITICAL, "Tenant: "+args.Tenant+" doesn't exist.", 1) + if not config.has("TENANTS:"+args.tenant): + log.info("Tenant: "+args.tenant+" doesn't exist.") + sys.exit(1) - # call update profiles - profile_mgr = ArgoProfileManager(args.ConfigPath) - profile_mgr.profile_update_check(args.Tenant, args.Report) + # check and upload recomputations + upload_recomputations(args.tenant, args.report, args.date, config) + # optional call to update profiles + if args.profile_check: + profile_mgr = ArgoProfileManager(config) + profile_type_checklist = ["operations", "aggregations", "reports", "thresholds"] + for profile_type in profile_type_checklist: + profile_mgr.profile_update_check(args.tenant, args.report, profile_type) # dictionary containing the argument's name and the command assosciated with each name - hdfs_commands = compose_hdfs_commands(year, month, day, args, config, logger) + hdfs_commands = compose_hdfs_commands(year, month, day, args, config) - cmd_command = compose_command(config, args, hdfs_commands, logger) + cmd_command = compose_command(config, args, hdfs_commands) - logger.print_and_log(logging.INFO, "Getting ready to submit job") - logger.print_and_log(logging.INFO, cmd_toString(cmd_command)+"\n") + log.info("Getting ready to submit job") + log.info(cmd_to_string(cmd_command)+"\n") # submit the script's command - flink_job_submit(config, logger, cmd_command) + flink_job_submit(config, cmd_command) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Batch A/R Job submit script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True, dest="tenant") parser.add_argument( - "-r", "--Report", type=str, help="Report status", required=True) + "-r", "--report", metavar="STRING", help="Report status", required=True, dest="report") parser.add_argument( - "-d", "--Date", type=str, help="Date to run the job for", required=True) + "-d", "--date", metavar="DATE(YYYY-MM-DD)", help="Date to run the job for", required=True, dest="date") parser.add_argument( - "-m", "--Method", type=str, help="Insert or Upsert data in mongoDB", required=True) + "-m", "--method", metavar="KEYWORD(insert|upsert)", help="Insert or Upsert data in mongoDB", required=True, dest="method") parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="PATH", help="Path for the config file", dest="config") parser.add_argument( - "-u", "--Sudo", help="Run the submition as superuser", action="store_true") + "-u", "--sudo", help="Run the submition as superuser", action="store_true") + parser.add_argument("--profile-check", help="check if profiles are up to date before running job", + dest="profile_check", action="store_true") + parser.add_argument("--thresholds", help="check and use threshold rule file if exists", + dest="thresholds", action="store_true") # Pass the arguments to main method sys.exit(main(parser.parse_args())) diff --git a/bin/metric-publisher.py b/bin/metric-publisher.py index 7678a7a1..76410c95 100755 --- a/bin/metric-publisher.py +++ b/bin/metric-publisher.py @@ -27,11 +27,10 @@ import logging import logging.handlers +log = logging.getLogger(__name__) -from time import sleep - -def publish(message, endpoint, project, topic, key,log): +def publish(message, endpoint, project, topic, key): url = "https://" + endpoint + "/v1/projects/"+project+"/topics/" + topic + ":publish?key=" + key log.debug("publishing to: " + url) r = requests.post(url, data=message, timeout=10) @@ -41,10 +40,8 @@ def publish(message, endpoint, project, topic, key,log): log.debug(r.text) - - def main(args): - log = logging.getLogger(__name__) + log.setLevel(logging.INFO) sys_log = logging.handlers.SysLogHandler("/dev/log") @@ -69,7 +66,8 @@ def main(args): json_str = json.dumps(msg) log.debug("json msg:" + json_str) - publish(json_str, args.ams_endpoint, args.ams_project, args.ams_topic, args.ams_key, log) + publish(json_str, args.ams_endpoint, args.ams_project, args.ams_topic, args.ams_key) + if __name__ == "__main__": @@ -77,15 +75,15 @@ def main(args): # (input_file,output_file,schema_file) arg_parser = ArgumentParser(description="Read a consumer avro file and publish rows to AMS") arg_parser.add_argument( - "-f", "--file", help="consumer avro file ", dest="avro_file", metavar="STRING", required="TRUE") + "-f", "--file", help="consumer avro file ", dest="avro_file", metavar="STRING", required=True) arg_parser.add_argument( - "-e", "--endpoint", help="ams endpoint", dest="ams_endpoint", metavar="STRING", required="TRUE") + "-e", "--endpoint", help="ams endpoint", dest="ams_endpoint", metavar="STRING", required=True) arg_parser.add_argument( - "-k", "--key", help="ams key(token) ", dest="ams_key", metavar="STRING", required="TRUE") + "-k", "--key", help="ams key(token) ", dest="ams_key", metavar="STRING", required=True) arg_parser.add_argument( - "-p", "--project", help="ams project ", dest="ams_project", metavar="STRING", required="TRUE") + "-p", "--project", help="ams project ", dest="ams_project", metavar="STRING", required=True) arg_parser.add_argument( - "-t", "--topic", help="ams topic ", dest="ams_topic", metavar="STRING", required="TRUE") + "-t", "--topic", help="ams topic ", dest="ams_topic", metavar="STRING", required=True) # Parse the command line arguments accordingly and introduce them to # main... diff --git a/bin/metric_ingestion_submit.py b/bin/metric_ingestion_submit.py index a32a8098..47ec738b 100755 --- a/bin/metric_ingestion_submit.py +++ b/bin/metric_ingestion_submit.py @@ -1,38 +1,32 @@ #!/usr/bin/env python import sys -import os import argparse -import ConfigParser import logging -from utils.argo_log import ArgoLogger -from utils.common import cmd_toString, flink_job_submit +from utils.common import cmd_to_string, flink_job_submit, get_config_paths, get_log_conf +from utils.argo_config import ArgoConfig +log = logging.getLogger(__name__) -def compose_command(config, args, sudo, logger=None): - # job_namespace - job_namespace = config.get("JOB-NAMESPACE", "ingest-metric-namespace") +def compose_command(config, args): - # job sumbission command + # job submission command cmd_command = [] - if sudo is True: + if args.sudo is True: cmd_command.append("sudo") - # create a simple stream_handler whenever tetsing - if logger is None: - logger = ArgoLogger() + # tenant the job will run for + section_tenant = "TENANTS:"+args.tenant + section_tenant_job = section_tenant+":ingest-metric" + config.get(section_tenant, "ams_project") - # check if configuration for the given tenant exists - try: - # tenant the job will run for - tenant = "TENANTS:"+args.Tenant.upper() - tenant_job = tenant+":ingest-metric" - config.get(tenant, "ams_project") - logger.print_and_log(logging.INFO, "Starting building the submit command for tenant: " + args.Tenant.upper()) - except ConfigParser.NoSectionError as e: - logger.print_and_log(logging.CRITICAL, str(e), 1) + # get needed config params + job_namespace = config.get("JOB-NAMESPACE", "ingest-metric-namespace") + ams_endpoint = config.get("AMS", "endpoint") + ams_project = config.get(section_tenant, "ams_project") + ams_sub = config.get(section_tenant_job, "ams_sub") # flink executable cmd_command.append(config.get("FLINK", "path")) @@ -44,108 +38,113 @@ def compose_command(config, args, sudo, logger=None): # Job's class inside the jar cmd_command.append(config.get("CLASSES", "ams-ingest-metric")) - # jar to be sumbitted to flink + # jar to be submitted to flink cmd_command.append(config.get("JARS", "ams-ingest-metric")) # ams endpoint cmd_command.append("--ams.endpoint") - cmd_command.append(config.get("AMS", "ams_endpoint")) - job_namespace = job_namespace.replace("{{ams_endpoint}}", config.get("AMS", "ams_endpoint")) + cmd_command.append(ams_endpoint.hostname) # ams port + ams_port = 443 + if ams_endpoint.port is not None: + ams_port = ams_endpoint.port cmd_command.append("--ams.port") - cmd_command.append(config.get("AMS", "ams_port")) - job_namespace = job_namespace.replace("{{ams_port}}", config.get("AMS", "ams_port")) + cmd_command.append(str(ams_port)) # tenant token cmd_command.append("--ams.token") - cmd_command.append(config.get(tenant, "ams_token")) + cmd_command.append(config.get(section_tenant, "ams_token")) # project/tenant cmd_command.append("--ams.project") - cmd_command.append(config.get(tenant, "ams_project")) - job_namespace = job_namespace.replace("{{project}}", config.get(tenant, "ams_project")) + cmd_command.append(ams_project) # ams subscription cmd_command.append("--ams.sub") - cmd_command.append(config.get(tenant_job, "ams_sub")) - job_namespace = job_namespace.replace("{{ams_sub}}", config.get(tenant_job, "ams_sub")) - - # hdfs path for the tenant - hdfs_metric = config.get("HDFS", "hdfs_metric") - hdfs_metric = hdfs_metric.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_metric = hdfs_metric.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_metric = hdfs_metric.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_metric = hdfs_metric.replace("{{tenant}}", args.Tenant.upper()) + cmd_command.append(ams_sub) + + # fill job_namespace template + job_namespace = job_namespace.fill(ams_endpoint=ams_endpoint.hostname, ams_port=ams_endpoint.port, + ams_project=ams_project, ams_sub=ams_sub) + + # set up the hdfs client to be used in order to check the files + namenode = config.get("HDFS", "namenode") + hdfs_user = config.get("HDFS", "user") + + hdfs_metric = config.get("HDFS", "path_metric") + hdfs_metric.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=args.tenant) + + hdfs_metric = hdfs_metric.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=args.tenant).geturl() + cmd_command.append("--hdfs.path") cmd_command.append(hdfs_metric) # path to store flink checkpoints cmd_command.append("--check.path") - cmd_command.append(config.get(tenant_job, "checkpoint_path")) + cmd_command.append(str(config.get(section_tenant_job, "checkpoint_path"))) # interval for checkpont in ms cmd_command.append("--check.interval") - cmd_command.append(config.get(tenant_job, "checkpoint_interval")) + cmd_command.append(str(config.get(section_tenant_job, "checkpoint_interval"))) # num of messages to be retrieved from AMS per request cmd_command.append("--ams.batch") - cmd_command.append(config.get(tenant_job, "ams_batch")) + cmd_command.append(str(config.get(section_tenant_job, "ams_batch"))) # interval in ms betweeb AMS service requests cmd_command.append("--ams.interval") - cmd_command.append(config.get(tenant_job, "ams_interval")) + cmd_command.append(str(config.get(section_tenant_job, "ams_interval"))) - # ams proxy - if config.getboolean("AMS", "proxy_enabled"): + # get optional ams proxy + proxy = config.get("AMS", "proxy") + if proxy is not None: cmd_command.append("--ams.proxy") - cmd_command.append(config.get("AMS", "ams_proxy")) + cmd_command.append(proxy.geturl()) # ssl verify cmd_command.append("--ams.verify") - if config.getboolean("AMS", "ssl_enabled"): - cmd_command.append("true") + ams_verify = config.get("AMS", "verify") + if ams_verify is not None: + cmd_command.append(str(ams_verify).lower()) else: - cmd_command.append("false") - + # by default assume ams verify is always true + cmd_command.append("true") + return cmd_command, job_namespace def main(args=None): + # Get configuration paths + conf_paths = get_config_paths(args.config) - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../conf/conf.cfg") - else: - config.read(args.ConfigPath) + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) - # set up the logger - logger = ArgoLogger(log_name="ingest-metric", config=config) + # check if configuration for the given tenant exists + if not config.has("TENANTS:" + args.tenant): + log.info("Tenant: " + args.tenant + " doesn't exist.") + sys.exit(1) - cmd_command, job_namespace = compose_command(config, args, args.Sudo, logger) + cmd_command, job_namespace = compose_command(config, args) - logger.print_and_log(logging.INFO, "Getting ready to submit job") - logger.print_and_log(logging.INFO, cmd_toString(cmd_command)+"\n") + log.info("Getting ready to submit job") + log.info(cmd_to_string(cmd_command)+"\n") # submit script's command - flink_job_submit(config, logger, cmd_command, job_namespace) + flink_job_submit(config, cmd_command, job_namespace) if __name__ == "__main__": parser = argparse.ArgumentParser(description="AMS Metric Ingestion submission script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True) parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="PATH", help="Path for the config file") parser.add_argument( - "-u", "--Sudo", help="Run the submition as superuser", action="store_true") + "-u", "--sudo", help="Run the submition as superuser", action="store_true") sys.exit(main(parser.parse_args())) diff --git a/bin/requirements.txt b/bin/requirements.txt index c892da41..64fb15a3 100644 --- a/bin/requirements.txt +++ b/bin/requirements.txt @@ -1,4 +1,5 @@ -requests==2.18.4 +requests==2.20.0 +responses==0.6.0 pytest==3.4.0 snakebite==2.11.0 pymongo==3.6.1 diff --git a/bin/status_job_submit.py b/bin/status_job_submit.py index bd825559..889c33c5 100755 --- a/bin/status_job_submit.py +++ b/bin/status_job_submit.py @@ -4,84 +4,92 @@ import argparse import datetime from snakebite.client import Client -import ConfigParser + import logging from urlparse import urlparse -from utils.argo_log import ArgoLogger from utils.argo_mongo import ArgoMongoClient -from utils.common import cmd_toString, date_rollback, flink_job_submit, hdfs_check_path +from utils.common import cmd_to_string, date_rollback, flink_job_submit, hdfs_check_path, get_log_conf, get_config_paths from utils.update_profiles import ArgoProfileManager +from utils.argo_config import ArgoConfig + +log = logging.getLogger(__name__) -def compose_hdfs_commands(year, month, day, args, config, logger): + +def compose_hdfs_commands(year, month, day, args, config): # set up the hdfs client to be used in order to check the files - client = Client(config.get("HDFS", "hdfs_host"), config.getint("HDFS", "hdfs_port"), use_trash=False) + namenode = config.get("HDFS", "namenode") + client = Client(namenode.hostname, namenode.port, use_trash=False) # hdfs sync path for the tenant - hdfs_sync = config.get("HDFS", "hdfs_sync") - hdfs_sync = hdfs_sync.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_sync = hdfs_sync.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_sync = hdfs_sync.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_sync = hdfs_sync.replace("{{tenant}}", args.Tenant) - - # hdfs metric path for the tenant - hdfs_metric = config.get("HDFS", "hdfs_metric") - hdfs_metric = hdfs_metric.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_metric = hdfs_metric.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_metric = hdfs_metric.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_metric = hdfs_metric.replace("{{tenant}}", args.Tenant) + + hdfs_user = config.get("HDFS", "user") + tenant = args.tenant + hdfs_sync = config.get("HDFS", "path_sync") + hdfs_sync = hdfs_sync.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() + + hdfs_metric = config.get("HDFS", "path_metric") + + hdfs_metric = hdfs_metric.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() # dictionary holding all the commands with their respective arguments' name - hdfs_commands = {} + hdfs_commands = dict() # file location of previous day's metric data (local or hdfs) - hdfs_commands["--pdata"] = hdfs_check_path(hdfs_metric+"/"+str(datetime.date(year, month, day) - datetime.timedelta(1)), logger, client) + hdfs_commands["--pdata"] = hdfs_check_path( + hdfs_metric + "/" + str(datetime.date(year, month, day) - datetime.timedelta(1)), client) # file location of target day's metric data (local or hdfs) - hdfs_commands["--mdata"] = hdfs_check_path(hdfs_metric+"/"+args.Date, logger, client) + hdfs_commands["--mdata"] = hdfs_check_path(hdfs_metric+"/"+args.date, client) # file location of report configuration json file (local or hdfs) - hdfs_commands["--conf"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_"+args.Report+"_cfg.json", logger, client) + hdfs_commands["--conf"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_"+args.report+"_cfg.json", client) # file location of metric profile (local or hdfs) - hdfs_commands["--mps"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"metric_profile_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["--mps"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "metric_profile_" + "{{date}}" + ".avro", year, month, day, config, + client) # file location of operations profile (local or hdfs) - hdfs_commands["--ops"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_ops.json", logger, client) + hdfs_commands["--ops"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_ops.json", client) # file location of aggregations profile (local or hdfs) - hdfs_commands["--apr"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_"+args.Report+"_ap.json", logger, client) + hdfs_commands["--apr"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_"+args.report+"_ap.json", client) + + if args.thresholds: + # file location of thresholds rules file (local or hdfs) + hdfs_commands["--thr"] = hdfs_check_path( + os.path.join(hdfs_sync, "".join([args.tenant, "_", args.report, "_thresholds.json"])), client) # file location of endpoint group topology file (local or hdfs) - hdfs_commands["-egp"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"group_endpoints_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["-egp"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "group_endpoints_" + "{{date}}" + ".avro", year, month, day, config, + client) # file location of group of groups topology file (local or hdfs) - hdfs_commands["-ggp"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"group_groups_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["-ggp"] = date_rollback(hdfs_sync + "/" + args.report + "/" + "group_groups_" + "{{date}}" + ".avro", + year, month, day, config, client) # file location of recomputations file (local or hdfs) # first check if there is a recomputations file for the given date - if client.test(urlparse(hdfs_sync+"/recomp_"+args.Date+".json").path, exists=True): - hdfs_commands["--rec"] = hdfs_sync+"/recomp_"+args.Date+".json" - logger.print_and_log(logging.INFO, "Using recomputations file for the given date") + if client.test(urlparse(hdfs_sync+"/recomp_"+args.date+".json").path, exists=True): + hdfs_commands["--rec"] = hdfs_sync+"/recomp_"+args.date+".json" + log.info("Using recomputations file for the given date") else: - hdfs_commands["--rec"] = hdfs_check_path(hdfs_sync+"/recomp.json", logger, client) - logger.print_and_log(logging.INFO, "Recomputations file for the given date was not found. Using default.") + hdfs_commands["--rec"] = hdfs_check_path(hdfs_sync+"/recomp.json", client) + log.info("Recomputations file for the given date was not found. Using default.") return hdfs_commands -def compose_command(config, args, hdfs_commands, logger=None): +def compose_command(config, args, hdfs_commands): # job sumbission command cmd_command = [] - if args.Sudo is True: + if args.sudo is True: cmd_command.append("sudo") - # create a simple stream_handler whenever tetsing - if logger is None: - logger = ArgoLogger() - # flink executable cmd_command.append(config.get("FLINK", "path")) @@ -97,104 +105,104 @@ def compose_command(config, args, hdfs_commands, logger=None): # date the report will run for cmd_command.append("--run.date") - cmd_command.append(args.Date) + cmd_command.append(args.date) # MongoDB uri for outputting the results to (e.g. mongodb://localhost:21017/example_db) cmd_command.append("--mongo.uri") - mongo_tenant = "TENANTS:"+args.Tenant+":MONGO" - mongo_uri = config.get(mongo_tenant, "mongo_uri") - mongo_uri = mongo_uri.replace("{{mongo_host}}", config.get(mongo_tenant, "mongo_host")) - mongo_uri = mongo_uri.replace("{{mongo_port}}", config.get(mongo_tenant, "mongo_port")) - cmd_command.append(mongo_uri) - - if args.Method == "insert": - argo_mongo_client = ArgoMongoClient(args, config, logger, ["status_metrics", "status_endpoints", "status_services", "status_endpoint_groups"]) + group_tenant = "TENANTS:" + args.tenant + mongo_endpoint = config.get("MONGO","endpoint").geturl() + mongo_uri = config.get(group_tenant, "mongo_uri").fill(mongo_endpoint=mongo_endpoint,tenant=args.tenant) + cmd_command.append(mongo_uri.geturl()) + + if args.method == "insert": + argo_mongo_client = ArgoMongoClient(args, config, ["status_metrics", "status_endpoints", "status_services", + "status_endpoint_groups"]) + argo_mongo_client.mongo_clean_status(mongo_uri) # MongoDB method to be used when storing the results, either insert or upsert cmd_command.append("--mongo.method") - cmd_command.append(args.Method) + cmd_command.append(args.method) # add the hdfs commands for command in hdfs_commands: cmd_command.append(command) cmd_command.append(hdfs_commands[command]) - # ams proxy - if config.getboolean("AMS", "proxy_enabled"): + # get optional ams proxy + proxy = config.get("AMS", "proxy") + if proxy is not None: cmd_command.append("--ams.proxy") - cmd_command.append(config.get("AMS", "ams_proxy")) + cmd_command.append(proxy.geturl()) # ssl verify cmd_command.append("--ams.verify") - if config.getboolean("AMS", "ssl_enabled"): - cmd_command.append("true") + ams_verify = config.get("AMS", "verify") + if ams_verify is not None: + cmd_command.append(str(ams_verify).lower()) else: - cmd_command.append("false") + # by default assume ams verify is always true + cmd_command.append("true") return cmd_command def main(args=None): - # make sure the argument are in the correct form - args.Tenant = args.Tenant.upper() - args.Method = args.Method.lower() + # Get configuration paths + conf_paths = get_config_paths(args.config) - year, month, day = [int(x) for x in args.Date.split("-")] + # Get logger config file + get_log_conf(conf_paths['log']) - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../conf/conf.cfg") - else: - config.read(args.ConfigPath) - - # set up the logger - logger = ArgoLogger(log_name="batch-status", config=config) + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) # check if configuration for the given tenant exists - if not config.has_section("TENANTS:"+args.Tenant): - logger.print_and_log(logging.CRITICAL, "Tenant: "+args.Tenant+" doesn't exist.", 1) + if not config.has("TENANTS:"+args.tenant): + log.fatal("Tenant: "+args.tenant+" doesn't exist.") + sys.exit(1) + + year, month, day = [int(x) for x in args.date.split("-")] - # call update profiles - profile_mgr = ArgoProfileManager(args.ConfigPath) - profile_mgr.profile_update_check(args.Tenant, args.Report) + # optional call to update profiles + if args.profile_check: + profile_mgr = ArgoProfileManager(config) + profile_type_checklist = ["operations", "aggregations", "reports", "thresholds"] + for profile_type in profile_type_checklist: + profile_mgr.profile_update_check(args.tenant, args.report, profile_type) - # dictionary containing the argument's name and the command assosciated with each name - hdfs_commands = compose_hdfs_commands(year, month, day, args, config, logger) + # dictionary containing the argument's name and the command associated with each name + hdfs_commands = compose_hdfs_commands(year, month, day, args, config) - cmd_command = compose_command(config, args, hdfs_commands, logger) + cmd_command = compose_command(config, args, hdfs_commands) - logger.print_and_log(logging.INFO, "Getting ready to submit job") - logger.print_and_log(logging.INFO, cmd_toString(cmd_command)+"\n") + log.info("Getting ready to submit job") + log.info(cmd_to_string(cmd_command)+"\n") # submit the script's command - flink_job_submit(config, logger, cmd_command) + flink_job_submit(config, cmd_command) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Batch Status Job submit script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True, dest="tenant") parser.add_argument( - "-r", "--Report", type=str, help="Report status", required=True) + "-r", "--report", metavar="STRING", help="Report status", required=True, dest="report") parser.add_argument( - "-d", "--Date", type=str, help="Date to run the job for", required=True) + "-d", "--date", metavar="DATE(YYYY-MM-DD)", help="Date to run the job for", required=True, dest="date") parser.add_argument( - "-m", "--Method", type=str, help="Insert or Upsert data in mongoDB", required=True) + "-m", "--method", metavar="KEYWORD(insert|upsert)", help="Insert or Upsert data in mongoDB", required=True, dest="method") parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="PATH", help="Path for the config file", dest="config") parser.add_argument( - "-u", "--Sudo", help="Run the submition as superuser", action="store_true") + "-u", "--sudo", help="Run the submit job as superuser", action="store_true") + parser.add_argument("--profile-check", help="check if profiles are up to date before running job", + dest="profile_check", action="store_true") + parser.add_argument("--thresholds", help="check and use threshold rule file if exists", + dest="thresholds", action="store_true") # Pass the arguments to main method sys.exit(main(parser.parse_args())) diff --git a/bin/stream_status_job_submit.py b/bin/stream_status_job_submit.py index 5215c50c..144dae44 100755 --- a/bin/stream_status_job_submit.py +++ b/bin/stream_status_job_submit.py @@ -1,59 +1,66 @@ #!/usr/bin/env python import sys -import os import argparse import datetime from snakebite.client import Client -import ConfigParser import logging -from utils.argo_log import ArgoLogger -from utils.common import cmd_toString, date_rollback, flink_job_submit, hdfs_check_path +from utils.argo_config import ArgoConfig +from utils.common import cmd_to_string, date_rollback, flink_job_submit, hdfs_check_path, get_config_paths, get_log_conf +log = logging.getLogger(__name__) -def compose_hdfs_commands(year, month, day, args, config, logger): +def compose_hdfs_commands(year, month, day, args, config): # set up the hdfs client to be used in order to check the files - client = Client(config.get("HDFS", "hdfs_host"), config.getint("HDFS", "hdfs_port"), use_trash=False) + namenode = config.get("HDFS", "namenode") + client = Client(namenode.hostname, namenode.port, use_trash=False) # hdfs sync path for the tenant - hdfs_sync = config.get("HDFS", "hdfs_sync") - hdfs_sync = hdfs_sync.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_sync = hdfs_sync.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_sync = hdfs_sync.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_sync = hdfs_sync.replace("{{tenant}}", args.Tenant) + + hdfs_user = config.get("HDFS", "user") + tenant = args.tenant + hdfs_sync = config.get("HDFS", "path_sync") + hdfs_sync = hdfs_sync.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() # dictionary holding all the commands with their respective arguments' name - hdfs_commands = {} + hdfs_commands = dict() # file location of metric profile (local or hdfs) - hdfs_commands["--sync.mps"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"metric_profile_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["--sync.mps"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "metric_profile_" + "{{date}}" + ".avro", year, month, day, config, + client) # file location of operations profile (local or hdfs) - hdfs_commands["--sync.ops"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_ops.json", logger, client) + hdfs_commands["--sync.ops"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_ops.json", client) # file location of aggregations profile (local or hdfs) - hdfs_commands["--sync.apr"] = hdfs_check_path(hdfs_sync+"/"+args.Tenant+"_"+args.Report+"_ap.json", logger, client) + hdfs_commands["--sync.apr"] = hdfs_check_path(hdfs_sync+"/"+args.tenant+"_"+args.report+"_ap.json", client) # file location of endpoint group topology file (local or hdfs) - hdfs_commands["-sync.egp"] = date_rollback(hdfs_sync+"/"+args.Report+"/"+"group_endpoints_"+"{{date}}"+".avro", year, month, day, config, logger, client) + hdfs_commands["-sync.egp"] = date_rollback( + hdfs_sync + "/" + args.report + "/" + "group_endpoints_" + "{{date}}" + ".avro", year, month, day, config, + client) return hdfs_commands -def compose_command(config, args, hdfs_commands, logger=None): +def compose_command(config, args, hdfs_commands): - # job sumbission command + # job submission command cmd_command = [] - # job namespace on the flink manager - job_namespace = config.get("JOB-NAMESPACE", "stream-status-namespace") - - if args.Sudo is True: + if args.sudo is True: cmd_command.append("sudo") - # create a simple stream_handler whenever tetsing - if logger is None: - logger = ArgoLogger() + # get needed config params + section_tenant = "TENANTS:" + args.tenant + section_tenant_job = "TENANTS:" + args.tenant + ":stream-status" + job_namespace = config.get("JOB-NAMESPACE", "stream-status-namespace") + ams_endpoint = config.get("AMS", "endpoint") + + ams_project = config.get(section_tenant, "ams_project") + ams_sub_metric = config.get(section_tenant_job, "ams_sub_metric") + ams_sub_sync = config.get(section_tenant_job, "ams_sub_sync") # flink executable cmd_command.append(config.get("FLINK", "path")) @@ -65,37 +72,36 @@ def compose_command(config, args, hdfs_commands, logger=None): # Job's class inside the jar cmd_command.append(config.get("CLASSES", "stream-status")) - # jar to be sumbitted to flink + # jar to be submitted to flink cmd_command.append(config.get("JARS", "stream-status")) # ams endpoint cmd_command.append("--ams.endpoint") - cmd_command.append(config.get("AMS", "ams_endpoint")) - job_namespace = job_namespace.replace("{{ams_endpoint}}", config.get("AMS", "ams_endpoint")) + cmd_command.append(ams_endpoint.hostname) # ams port cmd_command.append("--ams.port") - cmd_command.append(config.get("AMS", "ams_port")) - job_namespace = job_namespace.replace("{{ams_port}}", config.get("AMS", "ams_port")) + cmd_command.append(ams_endpoint.port) # tenant's token for ams cmd_command.append("--ams.token") - cmd_command.append(config.get("TENANTS:"+args.Tenant, "ams_token")) + cmd_command.append(config.get(section_tenant, "ams_token")) # ams project cmd_command.append("--ams.project") - cmd_command.append(config.get("TENANTS:"+args.Tenant, "ams_project")) - job_namespace = job_namespace.replace("{{project}}", config.get("TENANTS:"+args.Tenant, "ams_project")) + cmd_command.append(ams_project) # ams sub metric cmd_command.append("--ams.sub.metric") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "ams.sub.metric")) - job_namespace = job_namespace.replace("{{ams_sub_metric}}", config.get("TENANTS:"+args.Tenant+":stream-status", "ams.sub.metric")) + cmd_command.append(ams_sub_metric) # ams sub sync cmd_command.append("--ams.sub.sync") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "ams.sub.sync")) - job_namespace = job_namespace.replace("{{ams_sub_sync}}", config.get("TENANTS:"+args.Tenant+":stream-status", "ams.sub.sync")) + cmd_command.append(ams_sub_sync) + + # fill job namespace template with the required arguments + job_namespace.fill(ams_endpoint=ams_endpoint.hostname, ams_port=ams_endpoint.port, ams_project=ams_project, + ams_sub_metric=ams_sub_metric, ams_sub_sync=ams_sub_sync) # add the hdfs commands for command in hdfs_commands: @@ -104,159 +110,152 @@ def compose_command(config, args, hdfs_commands, logger=None): # date cmd_command.append("--run.date") - cmd_command.append(args.Date) + cmd_command.append(args.date) # flink parallelism cmd_command.append("--p") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "flink_parallelism")) + cmd_command.append(config.get(section_tenant_job, "flink_parallelism")) + + # grab tenant configuration section for stream-status - outputs = config.get("TENANTS:"+args.Tenant+":stream-status", "outputs").split(",") + outputs = config.get(section_tenant_job, "outputs") if len(outputs) == 0: - logger.print_and_log(logging.INFO, "No output formats found.") + log.fatal("No output formats found.") + sys.exit(1) else: for output in outputs: if output == "hbase": # hbase endpoint - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.master"): + if config.has(section_tenant_job, "hbase_master"): cmd_command.append("--hbase.master") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.master")) - + cmd_command.append(config.get(section_tenant_job, "hbase_master").hostname) # hbase endpoint port - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.master.port"): + if config.has(section_tenant_job, "hbase_master"): cmd_command.append("--hbase.port") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.master.port")) + cmd_command.append(config.get(section_tenant_job, "hbase_master").port) # comma separate list of zookeeper servers - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.zk.quorum"): + if config.has(section_tenant_job, "hbase_zk_quorum"): cmd_command.append("--hbase.zk.quorum") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.zk.quorum")) + cmd_command.append(config.get(section_tenant_job, "hbase_zk_quorum")) # port used by zookeeper servers - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.zk.port"): + if config.has(section_tenant_job, "hbase_zk_port"): cmd_command.append("--hbase.zk.port") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.zk.port")) + cmd_command.append(config.get(section_tenant_job, "hbase_zk_port")) # table namespace, usually tenant - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.namespace"): + if config.has(section_tenant_job, "hbase_namespace"): cmd_command.append("--hbase.namespace") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.namespace")) + cmd_command.append(config.get(section_tenant_job, "hbase_namespace")) # table name, usually metric data - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "hbase.table"): + if config.has(section_tenant_job, "hbase_table"): cmd_command.append("--hbase.table") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "hbase.table")) + cmd_command.append(config.get(section_tenant_job, "hbase_table")) elif output == "kafka": # kafka list of servers - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "kafka.servers"): + if config.has(section_tenant_job, "kafka_servers"): cmd_command.append("--kafka.servers") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "kafka.servers")) + kafka_servers = ','.join(config.get(section_tenant_job, "kafka_servers")) + cmd_command.append(kafka_servers) # kafka topic to send status events to - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "kafka.topic"): + if config.has(section_tenant_job, "kafka_topic"): cmd_command.append("--kafka.topic") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "kafka.topic")) + cmd_command.append(config.get(section_tenant_job, "kafka_topic")) elif output == "fs": # filesystem path for output(use "hdfs://" for hdfs path) - if logger.config_str_validator(config, "TENANTS:"+args.Tenant+":stream-status", "fs.output"): + if config.has(section_tenant_job, "fs_output"): cmd_command.append("--fs.output") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "fs.output")) - - if config.getboolean("TENANTS:"+args.Tenant+":stream-status", "use_mongo"): - # MongoDB uri for outputting the results to (e.g. mongodb://localhost:21017/example_db) - cmd_command.append("--mongo.uri") - mongo_tenant = "TENANTS:"+args.Tenant+":MONGO" - mongo_uri = config.get(mongo_tenant, "mongo_uri") - mongo_uri = mongo_uri.replace("{{mongo_host}}", config.get(mongo_tenant, "mongo_host")) - mongo_uri = mongo_uri.replace("{{mongo_port}}", config.get(mongo_tenant, "mongo_port")) - cmd_command.append(mongo_uri) - - # mongo method - cmd_command.append("--mongo.method") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "mongo_method")) + cmd_command.append(config.get(section_tenant_job, "fs_output")) + elif output == "mongo": + cmd_command.append("--mongo.uri") + mongo_endpoint = config.get("MONGO","endpoint").geturl() + mongo_uri = config.get(section_tenant, "mongo_uri").fill(mongo_endpoint=mongo_endpoint,tenant=args.tenant) + cmd_command.append(mongo_uri.geturl()) + # mongo method + cmd_command.append("--mongo.method") + cmd_command.append(config.get(section_tenant_job, "mongo_method")) # num of messages to be retrieved from AMS per request cmd_command.append("--ams.batch") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "ams_batch")) + cmd_command.append(config.get(section_tenant_job, "ams_batch")) # interval in ms betweeb AMS service requests cmd_command.append("--ams.interval") - cmd_command.append(config.get("TENANTS:"+args.Tenant+":stream-status", "ams_interval")) + cmd_command.append(config.get(section_tenant_job, "ams_interval")) - # ams proxy - if config.getboolean("AMS", "proxy_enabled"): + # get optional ams proxy + proxy = config.get("AMS", "proxy") + if proxy is not None: cmd_command.append("--ams.proxy") - cmd_command.append(config.get("AMS", "ams_proxy")) + cmd_command.append(proxy.geturl()) # ssl verify cmd_command.append("--ams.verify") - if config.getboolean("AMS", "ssl_enabled"): - cmd_command.append("true") + ams_verify = config.get("AMS", "verify") + if ams_verify is not None: + cmd_command.append(str(ams_verify).lower()) else: - cmd_command.append("false") + # by default assume ams verify is always true + cmd_command.append("true") - if args.Timeout is not None: + if args.timeout is not None: cmd_command.append("--timeout") - cmd_command.append(args.Timeout) + cmd_command.append(args.timeout) return cmd_command, job_namespace def main(args=None): - # make sure the argument are in the correct form - args.Tenant = args.Tenant.upper() - - - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../conf/conf.cfg") - else: - config.read(args.ConfigPath) + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) - # set up the logger - logger = ArgoLogger(log_name="batch-status", config=config) + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) # check if configuration for the given tenant exists - if not config.has_section("TENANTS:"+args.Tenant): - logger.print_and_log(logging.CRITICAL, "Tenant: "+args.Tenant+" doesn't exist.", 1) + if not config.has("TENANTS:" + args.tenant): + log.info("Tenant: " + args.tenant + " doesn't exist.") + sys.exit(1) - year, month, day = [int(x) for x in args.Date.split("T")[0].split("-")] + year, month, day = [int(x) for x in args.date.split("T")[0].split("-")] # dictionary containing the argument's name and the command assosciated with each name - hdfs_commands = compose_hdfs_commands(year, month, day, args, config, logger) + hdfs_commands = compose_hdfs_commands(year, month, day, args, config) - cmd_command, job_namespace = compose_command(config, args, hdfs_commands, logger) + cmd_command, job_namespace = compose_command(config, args, hdfs_commands) - logger.print_and_log(logging.INFO, "Getting ready to submit job") - logger.print_and_log(logging.INFO, cmd_toString(cmd_command)+"\n") + log.info("Getting ready to submit job") + log.info(cmd_to_string(cmd_command)+"\n") # submit the script's command - flink_job_submit(config, logger, cmd_command, job_namespace) + flink_job_submit(config, cmd_command, job_namespace) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Stream Status Job submit script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True, dest="tenant") parser.add_argument( - "-d", "--Date", type=str, default=str(datetime.datetime.utcnow().replace(microsecond=0).isoformat())+"Z", help="Date in ISO-8601 format") + "-d", "--date", metavar="DATE(ISO-8601)", + default=str(datetime.datetime.utcnow().replace(microsecond=0).isoformat()) + "Z", + help="Date in ISO-8601 format", dest="date") parser.add_argument( - "-r", "--Report", type=str, help="Report status", required=True) + "-r", "--report", metavar="STRING", help="Report status", required=True, dest="report") parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="PATH", help="Path for the config file", dest="config") parser.add_argument( - "-u", "--Sudo", help="Run the submition as superuser", action="store_true") + "-u", "--sudo", help="Run the submition as superuser", action="store_true", dest="sudo") parser.add_argument( - "-timeout", "--Timeout", type=str, help="Controls default timeout for event regeneration (used in notifications)") + "-timeout", "--timeout", metavar="INT", + help="Controls default timeout for event regeneration (used in notifications)", dest="timeout") # Pass the arguments to main method sys.exit(main(parser.parse_args())) diff --git a/bin/sync_ingestion_submit.py b/bin/sync_ingestion_submit.py index 408200c7..48b52b78 100755 --- a/bin/sync_ingestion_submit.py +++ b/bin/sync_ingestion_submit.py @@ -1,38 +1,33 @@ #! /usr/bin/env python import sys -import os import argparse -import ConfigParser import logging -from utils.argo_log import ArgoLogger -from utils.common import cmd_toString, flink_job_submit +from utils.common import cmd_to_string, flink_job_submit, get_log_conf, get_config_paths +from utils.argo_config import ArgoConfig +log = logging.getLogger(__name__) -def compose_command(config, args, sudo, logger=None): - # job_namespace - job_namespace = config.get("JOB-NAMESPACE", "ingest-sync-namespace") +def compose_command(config, args): - # job sumbission command + # job submission command cmd_command = [] - if sudo is True: + if args.sudo is True: cmd_command.append("sudo") - # create a simple stream_handler whenever tetsing - if logger is None: - logger = ArgoLogger() + # tenant the job will run for + section_tenant = "TENANTS:" + args.tenant + section_tenant_job = section_tenant + ":ingest-sync" + config.get(section_tenant, "ams_project") - # check if configuration for the given tenant exists - try: - # tenant the job will run for - tenant = "TENANTS:"+args.Tenant.upper() - tenant_job = tenant+":ingest-sync" - config.get(tenant, "ams_project") - logger.print_and_log(logging.INFO, "Starting building the submit command for tenant: " + args.Tenant.upper()) - except ConfigParser.NoSectionError as e: - logger.print_and_log(logging.CRITICAL, str(e), 1) + # get needed config params + job_namespace = config.get("JOB-NAMESPACE", "ingest-sync-namespace") + ams_endpoint = config.get("AMS", "endpoint") + + ams_project = config.get(section_tenant, "ams_project") + ams_sub = config.get(section_tenant_job, "ams_sub") # flink executable cmd_command.append(config.get("FLINK", "path")) @@ -49,95 +44,102 @@ def compose_command(config, args, sudo, logger=None): # ams endpoint cmd_command.append("--ams.endpoint") - cmd_command.append(config.get("AMS", "ams_endpoint")) - job_namespace = job_namespace.replace("{{ams_endpoint}}", config.get("AMS", "ams_endpoint")) + cmd_command.append(ams_endpoint.hostname) # ams port + ams_port = 443 + if ams_endpoint.port is not None: + ams_port = ams_endpoint.port cmd_command.append("--ams.port") - cmd_command.append(config.get("AMS", "ams_port")) - job_namespace = job_namespace.replace("{{ams_port}}", config.get("AMS", "ams_port")) + cmd_command.append(str(ams_port)) # tenant token cmd_command.append("--ams.token") - cmd_command.append(config.get(tenant, "ams_token")) + cmd_command.append(config.get(section_tenant, "ams_token")) # project/tenant cmd_command.append("--ams.project") - cmd_command.append(config.get(tenant, "ams_project")) - job_namespace = job_namespace.replace("{{project}}", config.get(tenant, "ams_project")) + cmd_command.append(ams_project) # ams subscription cmd_command.append("--ams.sub") - cmd_command.append(config.get(tenant_job, "ams_sub")) - job_namespace = job_namespace.replace("{{ams_sub}}", config.get(tenant_job, "ams_sub")) - - # hdfs path for the tenant - hdfs_metric = config.get("HDFS", "hdfs_sync") - hdfs_metric = hdfs_metric.replace("{{hdfs_host}}", config.get("HDFS", "hdfs_host")) - hdfs_metric = hdfs_metric.replace("{{hdfs_port}}", config.get("HDFS", "hdfs_port")) - hdfs_metric = hdfs_metric.replace("{{hdfs_user}}", config.get("HDFS", "hdfs_user")) - hdfs_metric = hdfs_metric.replace("{{tenant}}", args.Tenant.upper()) + cmd_command.append(ams_sub) + + # fill job_namespace template + job_namespace = job_namespace.fill(ams_endpoint=ams_endpoint.hostname, ams_port=ams_port, + ams_project=ams_project, ams_sub=ams_sub) + + # set up the hdfs client to be used in order to check the files + namenode = config.get("HDFS", "namenode") + hdfs_user = config.get("HDFS", "user") + + hdfs_sync = config.get("HDFS", "path_sync") + hdfs_sync.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=args.tenant) + + hdfs_sync = hdfs_sync.fill(namenode=namenode.geturl(), hdfs_user=hdfs_user, tenant=args.tenant).geturl() + + # append hdfs sync base path to the submit command cmd_command.append("--hdfs.path") - cmd_command.append(hdfs_metric) + cmd_command.append(hdfs_sync) # num of messages to be retrieved from AMS per request cmd_command.append("--ams.batch") - cmd_command.append(config.get(tenant_job, "ams_batch")) + cmd_command.append(str(config.get(section_tenant_job, "ams_batch"))) # interval in ms betweeb AMS service requests cmd_command.append("--ams.interval") - cmd_command.append(config.get(tenant_job, "ams_interval")) + cmd_command.append(str(config.get(section_tenant_job, "ams_interval"))) - # ams proxy - if config.getboolean("AMS", "proxy_enabled"): + # get optional ams proxy + proxy = config.get("AMS", "proxy") + if proxy is not None: cmd_command.append("--ams.proxy") - cmd_command.append(config.get("AMS", "ams_proxy")) + cmd_command.append(proxy.geturl()) # ssl verify cmd_command.append("--ams.verify") - if config.getboolean("AMS", "ssl_enabled"): - cmd_command.append("true") + ams_verify = config.get("AMS", "verify") + if ams_verify is not None: + cmd_command.append(str(ams_verify).lower()) else: - cmd_command.append("false") + # by default assume ams verify is always true + cmd_command.append("true") return cmd_command, job_namespace def main(args=None): + # Get configuration paths + conf_paths = get_config_paths(args.config) - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../conf/conf.cfg") - else: - config.read(args.ConfigPath) + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) - # set up the logger - logger = ArgoLogger(log_name="ingest-sync", config=config) + # check if configuration for the given tenant exists + if not config.has("TENANTS:" + args.tenant): + log.info("Tenant: " + args.tenant + " doesn't exist.") + sys.exit(1) - cmd_command, job_namespace = compose_command(config, args, args.Sudo, logger) + cmd_command, job_namespace = compose_command(config, args) - logger.print_and_log(logging.INFO, "Getting ready to submit job") - logger.print_and_log(logging.INFO, cmd_toString(cmd_command)+"\n") + log.info("Getting ready to submit job") + log.info(cmd_to_string(cmd_command)+"\n") # submit the job - flink_job_submit(config, logger, cmd_command, job_namespace) + + flink_job_submit(config, cmd_command, job_namespace) if __name__ == "__main__": parser = argparse.ArgumentParser(description="AMS Sync Ingestion submission script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True) parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="PATH", help="Path for the config file") parser.add_argument( - "-u", "--Sudo", help="Run the submition as superuser", action="store_true") + "-u", "--sudo", help="Run the submission as superuser", action="store_true") sys.exit(main(parser.parse_args())) diff --git a/bin/test_ar_job_submit.py b/bin/test_ar_job_submit.py index 51cf61a7..7a1d6dcf 100644 --- a/bin/test_ar_job_submit.py +++ b/bin/test_ar_job_submit.py @@ -1,11 +1,35 @@ import unittest import argparse -import ConfigParser import os from ar_job_submit import compose_command -from utils.common import cmd_toString +from utils.common import cmd_to_string +from utils.argo_config import ArgoConfig CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../conf/conf.template') +CONF_SCHEMA = os.path.join(os.path.dirname(__file__), '../conf/config.schema.json') + +# This is the command that the submission script is expected to compose based on given args and config +expected_result = """flink_path run -c test_class test.jar --run.date 2018-02-11 \ +--mongo.uri mongodb://localhost:21017/argo_TENANTA --mongo.method upsert \ +--mdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-11 \ +--rec hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/recomp.json \ +--downtimes hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/downtimes_2018-02-11.avro \ +--mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/metric_profile_2018-02-11.avro \ +--apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/TENANTA_Critical_ap.json \ +--ggp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/group_groups_2018-02-11.avro \ +--conf hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/TENANTA_Critical_cfg.json \ +--egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/group_endpoints_2018-02-11.avro \ +--pdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/mdata/2018-02-10 --weights hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/weights_2018-02-11.avro \ +--ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_ops.json \ +--ams.proxy test_proxy --ams.verify true""" class TestClass(unittest.TestCase): @@ -13,21 +37,21 @@ class TestClass(unittest.TestCase): def test_compose_command(self): # set up the config parser - config = ConfigParser.ConfigParser() - config.read(CONF_TEMPLATE) + config = ArgoConfig(CONF_TEMPLATE, CONF_SCHEMA) parser = argparse.ArgumentParser() - parser.add_argument('--Tenant') - parser.add_argument('--Date') - parser.add_argument('--Report') - parser.add_argument('--Sudo', action='store_true') - parser.add_argument('--Method') - args = parser.parse_args(['--Tenant', 'TENANTA', '--Date', '2018-02-11', '--Report', 'Critical', '--Method', 'upsert']) + parser.add_argument('--tenant') + parser.add_argument('--date') + parser.add_argument('--report') + parser.add_argument('--sudo', action='store_true') + parser.add_argument('--method') + args = parser.parse_args( + ['--tenant', 'TENANTA', '--date', '2018-02-11', '--report', 'Critical', '--method', 'upsert']) hdfs_metric = "hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata" hdfs_sync = "hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync" - test_hdfs_commands = {} + test_hdfs_commands = dict() test_hdfs_commands["--pdata"] = hdfs_metric+"/2018-02-10" test_hdfs_commands["--mdata"] = hdfs_metric+"/2018-02-11" @@ -41,6 +65,4 @@ def test_compose_command(self): test_hdfs_commands["--downtimes"] = hdfs_sync+"/Critical/downtimes_2018-02-11.avro" test_hdfs_commands["--rec"] = hdfs_sync+"/recomp.json" - test_cmd = "flink_path run -c test_class test.jar --run.date 2018-02-11 --mongo.uri mongodb://mongo_test_host:mongo_test_port/argo_TENANTA --mongo.method upsert --mdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-11 --rec hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/recomp.json --downtimes hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/downtimes_2018-02-11.avro --mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/metric_profile_2018-02-11.avro --apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_ap.json --ggp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/group_groups_2018-02-11.avro --conf hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_cfg.json --egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/group_endpoints_2018-02-11.avro --pdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-10 --weights hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/weights_2018-02-11.avro --ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_ops.json --ams.proxy test_proxy --ams.verify true" - - self.assertEquals(test_cmd, cmd_toString(compose_command(config, args, test_hdfs_commands))) + self.assertEquals(expected_result, cmd_to_string(compose_command(config, args, test_hdfs_commands))) diff --git a/bin/test_metric_ingestion_submit.py b/bin/test_metric_ingestion_submit.py index 29d78e4d..e6928bb1 100644 --- a/bin/test_metric_ingestion_submit.py +++ b/bin/test_metric_ingestion_submit.py @@ -1,25 +1,32 @@ import unittest from metric_ingestion_submit import compose_command -from utils.common import cmd_toString -import ConfigParser +from utils.common import cmd_to_string +from utils.argo_config import ArgoConfig import argparse import os CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../conf/conf.template') +CONF_SCHEMA = os.path.join(os.path.dirname(__file__), '../conf/config.schema.json') + +# This is the command that the submission script is expected to compose based on given args and config +expected_result = """sudo flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port 8080 \ +--ams.token test_token --ams.project test_project --ams.sub job_name \ +--hdfs.path hdfs://hdfs_test_host:hdfs_test_host/user/hdfs_test_user/argo/tenants/TENANTA/mdata \ +--check.path test_path --check.interval 30000 --ams.batch 100 --ams.interval 300 --ams.proxy test_proxy \ +--ams.verify true""" class TestClass(unittest.TestCase): def test_compose_command(self): - # set up the config parser - config = ConfigParser.ConfigParser() - config.read(CONF_TEMPLATE) - - test_cmd = "sudo flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port test_port --ams.token test_token --ams.project test_project --ams.sub job_name --hdfs.path hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata --check.path test_path --check.interval 30000 --ams.batch 100 --ams.interval 300 --ams.proxy test_proxy --ams.verify true" + config = ArgoConfig(CONF_TEMPLATE, CONF_SCHEMA) parser = argparse.ArgumentParser() - parser.add_argument('--Tenant') - args = parser.parse_args(['--Tenant', 'TenantA']) + parser.add_argument('--tenant') + parser.add_argument('--sudo', action='store_true') + args = parser.parse_args(['--tenant', 'TENANTA', '--sudo']) + + print cmd_to_string(compose_command(config, args)[0]) - self.assertEquals(test_cmd, cmd_toString(compose_command(config, args, True)[0])) + self.assertEquals(expected_result, cmd_to_string(compose_command(config, args)[0])) diff --git a/bin/test_status_job_submit.py b/bin/test_status_job_submit.py index bc4103cd..a0029abd 100644 --- a/bin/test_status_job_submit.py +++ b/bin/test_status_job_submit.py @@ -1,33 +1,50 @@ import unittest import argparse -import ConfigParser +from utils.argo_config import ArgoConfig import os from status_job_submit import compose_command -from utils.common import cmd_toString +from utils.common import cmd_to_string CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../conf/conf.template') +CONF_SCHEMA = os.path.join(os.path.dirname(__file__), '../conf/config.schema.json') + +# This is the command that the submission script is expected to compose based on given args and config +expected_result = """sudo flink_path run -c test_class test.jar --run.date 2018-02-11 \ +--mongo.uri mongodb://localhost:21017/argo_TENANTA --mongo.method upsert \ +--mdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-11 \ +--mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/metric_profile_2018-02-11.avro \ +--apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/TENANTA_Critical_ap.json \ +--ggp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/group_groups_2018-02-11.avro \ +--conf hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_cfg.json \ +--egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/group_endpoints_2018-02-11.avro \ +--pdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-10 \ +--ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_ops.json \ +--ams.proxy test_proxy --ams.verify true""" class TestClass(unittest.TestCase): def test_compose_command(self): - # set up the config parser - config = ConfigParser.ConfigParser() - config.read(CONF_TEMPLATE) + config = ArgoConfig(CONF_TEMPLATE, CONF_SCHEMA) parser = argparse.ArgumentParser() - parser.add_argument('--Tenant') - parser.add_argument('--Date') - parser.add_argument('--Report') - parser.add_argument('--Sudo', action='store_true') - parser.add_argument('--Method') - args = parser.parse_args(['--Tenant', 'TENANTA', '--Date', '2018-02-11', '--Report', 'Critical', '--Method', 'upsert']) + parser.add_argument('--tenant') + parser.add_argument('--date') + parser.add_argument('--report') + parser.add_argument('--sudo', action='store_true') + parser.add_argument('--method') + args = parser.parse_args( + ['--tenant', 'TENANTA', '--date', '2018-02-11', '--report', 'Critical', '--method', 'upsert', '--sudo']) hdfs_metric = "hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata" hdfs_sync = "hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync" - test_hdfs_commands = {} + test_hdfs_commands = dict() test_hdfs_commands["--pdata"] = hdfs_metric+"/2018-02-10" test_hdfs_commands["--mdata"] = hdfs_metric+"/2018-02-11" @@ -38,6 +55,4 @@ def test_compose_command(self): test_hdfs_commands["--egp"] = hdfs_sync+"/Critical/group_endpoints_2018-02-11.avro" test_hdfs_commands["--ggp"] = hdfs_sync+"/Critical/group_groups_2018-02-11.avro" - test_cmd = "flink_path run -c test_class test.jar --run.date 2018-02-11 --mongo.uri mongodb://mongo_test_host:mongo_test_port/argo_TENANTA --mongo.method upsert --mdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-11 --mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/metric_profile_2018-02-11.avro --apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_ap.json --ggp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/group_groups_2018-02-11.avro --conf hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_cfg.json --egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/group_endpoints_2018-02-11.avro --pdata hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/mdata/2018-02-10 --ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_ops.json --ams.proxy test_proxy --ams.verify true" - - self.assertEquals(test_cmd, cmd_toString(compose_command(config, args, test_hdfs_commands))) + self.assertEquals(expected_result, cmd_to_string(compose_command(config, args, test_hdfs_commands))) diff --git a/bin/test_stream_status_job_submit.py b/bin/test_stream_status_job_submit.py index 60ba8b00..b2f1fd12 100644 --- a/bin/test_stream_status_job_submit.py +++ b/bin/test_stream_status_job_submit.py @@ -1,36 +1,50 @@ import unittest import argparse -import ConfigParser import os +from utils.argo_config import ArgoConfig from stream_status_job_submit import compose_command -from utils.common import cmd_toString +from utils.common import cmd_to_string CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../conf/conf.template') +CONF_SCHEMA = os.path.join(os.path.dirname(__file__), '../conf/config.schema.json') + +expected_result = """sudo flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port 8080 \ +--ams.token test_token --ams.project test_project --ams.sub.metric metric_status --ams.sub.sync sync_status \ +--sync.apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_ap.json \ +--sync.egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/group_endpoints_2018-03-01.avro \ +--sync.mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/\ +TENANTA/sync/Critical/metric_profile_2018-03-01.avro \ +--sync.ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/\ +sync/TENANTA_ops.json --run.date 2018-03-05T00:00:00Z --p 1 \ +--hbase.master hbase.devel --hbase.port 8080 --hbase.zk.quorum ['test_zk_servers'] \ +--hbase.zk.port 8080 --hbase.namespace test_hbase_namespace --hbase.table metric_data \ +--kafka.servers kafka_server:9090,kafka_server2:9092 --kafka.topic test_kafka_topic --fs.output None --mongo.uri mongodb://localhost:21017/argo_TENANTA --mongo.method upsert --ams.batch 10 --ams.interval 300 --ams.proxy test_proxy --ams.verify true --timeout 500""" + class TestClass(unittest.TestCase): def test_compose_command(self): - # set up the config parser - config = ConfigParser.ConfigParser() - config.read(CONF_TEMPLATE) + config = ArgoConfig(CONF_TEMPLATE, CONF_SCHEMA) parser = argparse.ArgumentParser() - parser.add_argument('--Tenant') - parser.add_argument('--Date') - parser.add_argument('--Report') - parser.add_argument('--Sudo', action='store_true') - parser.add_argument('--Timeout') - args = parser.parse_args(['--Tenant', 'TENANTA', '--Date', '2018-03-05T00:00:00Z', '--Report', 'Critical', '--Timeout', "500"]) + parser.add_argument('--tenant') + parser.add_argument('--date') + parser.add_argument('--report') + parser.add_argument('--sudo', action='store_true') + parser.add_argument('--timeout') + args = parser.parse_args( + ['--tenant', 'TENANTA', '--date', '2018-03-05T00:00:00Z', '--report', 'Critical', '--timeout', '500', + '--sudo']) + hdfs_sync = "hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync" - test_hdfs_commands = {} + test_hdfs_commands = dict() test_hdfs_commands["--sync.mps"] = hdfs_sync+"/Critical/"+"metric_profile_2018-03-01.avro" test_hdfs_commands["--sync.ops"] = hdfs_sync+"/TENANTA_ops.json" test_hdfs_commands["--sync.apr"] = hdfs_sync+"/TENANTA_Critical_ap.json" test_hdfs_commands["--sync.egp"] = hdfs_sync+"/Critical/group_endpoints_2018-03-01.avro" - test_cmd = "flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port test_port --ams.token test_token --ams.project test_project --ams.sub.metric metric_status --ams.sub.sync sync_status --sync.apr hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_Critical_ap.json --sync.egp hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/group_endpoints_2018-03-01.avro --sync.mps hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/Critical/metric_profile_2018-03-01.avro --sync.ops hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync/TENANTA_ops.json --run.date 2018-03-05T00:00:00Z --p 1 --hbase.master hbase.devel --hbase.port test_hbase_port --hbase.zk.quorum test_zk_servers --hbase.zk.port test_zk_port --hbase.namespace test_hbase_namespace --hbase.table metric_data --kafka.servers test_kafka_servers --kafka.topic test_kafka_topic --fs.output test_fs_output --mongo.uri mongodb://mongo_test_host:mongo_test_port/argo_TENANTA --mongo.method upsert --ams.batch test_batch --ams.interval test_interval --ams.proxy test_proxy --ams.verify true --timeout 500" - - self.assertEquals(test_cmd, cmd_toString(compose_command(config, args, test_hdfs_commands)[0])) + self.assertEquals(expected_result, cmd_to_string(compose_command(config, args, test_hdfs_commands)[0])) diff --git a/bin/test_sync_ingestion_submit.py b/bin/test_sync_ingestion_submit.py index 1631180a..d82c8d6e 100644 --- a/bin/test_sync_ingestion_submit.py +++ b/bin/test_sync_ingestion_submit.py @@ -1,11 +1,18 @@ import unittest from sync_ingestion_submit import compose_command -from utils.common import cmd_toString -import ConfigParser +from utils.common import cmd_to_string +from utils.argo_config import ArgoConfig import argparse import os CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../conf/conf.template') +CONF_SCHEMA = os.path.join(os.path.dirname(__file__), '../conf/config.schema.json') + +# This is the command that the submission script is expected to compose based on given args and config +expected_result = """sudo flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port 8080 \ +--ams.token test_token --ams.project test_project --ams.sub job_name \ +--hdfs.path hdfs://hdfs_test_host:hdfs_test_host/user/hdfs_test_user/argo/tenants/TENANTA/sync \ +--ams.batch 100 --ams.interval 3000 --ams.proxy test_proxy --ams.verify true""" class TestClass(unittest.TestCase): @@ -13,13 +20,11 @@ class TestClass(unittest.TestCase): def test_compose_command(self): # set up the config parser - config = ConfigParser.ConfigParser() - config.read(CONF_TEMPLATE) - - test_cmd = "sudo flink_path run -c test_class test.jar --ams.endpoint test_endpoint --ams.port test_port --ams.token test_token --ams.project test_project --ams.sub job_name --hdfs.path hdfs://hdfs_test_host:hdfs_test_port/user/hdfs_test_user/argo/tenants/TENANTA/sync --ams.batch 100 --ams.interval 3000 --ams.proxy test_proxy --ams.verify true" + config = ArgoConfig(CONF_TEMPLATE, CONF_SCHEMA) parser = argparse.ArgumentParser() - parser.add_argument('--Tenant') - args = parser.parse_args(['--Tenant', 'TenantA']) + parser.add_argument('--tenant') + parser.add_argument('--sudo', action='store_true') + args = parser.parse_args(['--tenant', 'TENANTA', '--sudo']) - self.assertEquals(test_cmd, cmd_toString(compose_command(config, args, True)[0])) + self.assertEquals(expected_result, cmd_to_string(compose_command(config, args)[0])) diff --git a/bin/update_engine.py b/bin/update_engine.py new file mode 100755 index 00000000..d5308e7d --- /dev/null +++ b/bin/update_engine.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +from argparse import ArgumentParser +from utils.common import get_config_paths, get_log_conf +import sys +import shutil +import os.path +import logging +import json +from datetime import datetime +from utils.argo_config import ArgoConfig +from utils.update_profiles import ArgoProfileManager +from utils.update_ams import ArgoAmsClient, is_tenant_complete +from utils.check_tenant import check_tenants, get_today +from utils.update_cron import gen_tenant_all, update_cron_tab +from snakebite.client import Client + + +log = logging.getLogger("argo.update_engine") + + +def main(args): + + if args.config is not None and not os.path.isfile(args.config): + log.info(args.config + " file not found") + + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + log.info("Argo-engine update stated.") + # if backup-conf selected backup the configuration file + if args.backup: + date_postfix = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + backup = args.config + "." + date_postfix + shutil.copyfile(args.config, backup) + log.info("backed-up current configuration to: " + backup) + + + argo_profiles = ArgoProfileManager(config) + argo_profiles.upload_tenants_cfg() + config.save_as(conf_paths["main"]) + + # reload config and profile manager + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + argo_profiles = ArgoProfileManager(config) + + tenants = config.get("API","tenants") + profile_type_checklist = ["operations", "aggregations", "reports", "thresholds", "recomputations"] + for tenant in tenants: + reports = config.get("TENANTS:"+tenant,"reports") + for report in reports: + for profile_type in profile_type_checklist: + argo_profiles.profile_update_check(tenant, report, profile_type) + + # update ams + ams_token = config.get("AMS", "access_token") + ams_host = config.get("AMS", "endpoint").hostname + log.info("ams api used {}".format(ams_host)) + + ams = ArgoAmsClient(ams_host, ams_token) + + for tenant in tenants: + ams.check_project_exists(tenant) + missing = ams.check_tenant(tenant) + if is_tenant_complete(missing): + log.info("Tenant {} definition on AMS is complete!".format(tenant)) + else: + ams.fill_missing(tenant, missing) + # Update tenant configuration + ams.update_tenant_configuration(tenant, config) + + # Save changes to designated configuration file + config.save_as(config.conf_path) + + # check tenant status + + # Upload tenant statuses in argo web api + api_endpoint = config.get("API","endpoint").netloc + api_token = config.get("API","access_token") + statuses = check_tenants(tenants,get_today(),3,config) + + # Update cron accordingly + cron_body = "" + for status in statuses: + cron_body = cron_body + gen_tenant_all(config,status["tenant"],tenant_ok_reports(status)) + update_cron_tab(cron_body) + log.info("Argo-engine update finished.") + +def tenant_ok_reports(status): + rep_list = list() + if status["hdfs"]["metric_data"] is False: + return rep_list + + for report_name in status["hdfs"]["sync_data"]: + result = 1 + report = status["hdfs"]["sync_data"][report_name] + for key in report.keys(): + result = result * report[key] + if result > 0: + rep_list.append(report_name) + return rep_list + + +if __name__ == "__main__": + + parser = ArgumentParser(description="Update engine") + parser.add_argument( + "-b", "--backup-conf", help="backup current configuration", action="store_true", dest="backup") + parser.add_argument( + "-c", "--config", metavar="PATH", help="Path for the config file", dest="config") + + # Pass the arguments to main method + sys.exit(main(parser.parse_args())) diff --git a/bin/utils/argo_config.py b/bin/utils/argo_config.py new file mode 100755 index 00000000..8a209d0d --- /dev/null +++ b/bin/utils/argo_config.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python +from ConfigParser import SafeConfigParser +from urlparse import urlparse +import json +import re +import logging +from os import path + + +log = logging.getLogger(__name__) + + +class Template: + """ + Implements option parameters that are templates + """ + + def __init__(self, tmpl, sub_type): + self.tmpl = tmpl + self.sub_type = sub_type + + def __repr__(self): + """ + String representation of template object + """ + return self.tmpl + + def get_args(self): + + """ + Get arguments used in template + + Returns: + list: a list of arguments used in the tamplate + """ + return re.findall(r"{{\s*(.*?)\s*}}", self.tmpl) + + def fill(self, **args_new): + """ + Fill template with argument values and get result + + Args: + args_new = list of argument key,value pairs fill the template + Returns: + obj: Fills template and returns an appropriate object based on template type + """ + txt = self.tmpl # type: str + args = self.get_args() + + # If provided arguments fill the needed ones we are ok (extra arguments will be ingored) + if not set(args).issubset(set(args_new.keys())): + raise RuntimeError("Argument mismatch, needed arguments:"+str(args)) + for arg in args: + txt = re.sub(r"{{\s*"+str(arg)+r"\s*}}", str(args_new[arg]), txt) + + return self.get_as(txt) + + def partial_fill(self, **args_new): + """ + Fill template partially with argument values and get result + + Args: + args_new = list of argument key,value pairs fill the template + Returns: + str: Fills partially a template and returns a string + """ + txt = self.tmpl # type: str + args = self.get_args() + + for arg in args: + if arg not in args_new.keys(): + continue + txt = re.sub(r"{{\s*" + str(arg) + r"\s*}}", str(args_new[arg]), txt) + + return txt + + def get_as(self, text): + """ + Get template result as a string and convert it to an appropriate return type + + Args: + text: str. template result + Returns: + obj: template result in appropriate type + """ + if self.sub_type == "string": + return str(text) + elif self.sub_type == "int" or self.sub_type == "long": + return int(text) + elif self.sub_type == "bool": + return bool(text) + elif self.sub_type == "float": + return float(text) + elif self.sub_type == "uri": + return urlparse(text) + elif self.sub_type == "list": + return text.split(",") + elif self.sub_type == "path": + return path.normpath(text) + + +class ArgoConfig: + """ + ArgoConfig implements a class that parser argo fixed and dynamic configuration + based on a specific schema + """ + + def __init__(self, config=None, schema=None): + self.log_changes = True + self.conf_path = None + self.schema_path = None + self.conf = SafeConfigParser() + self.schema = dict() + self.fix = dict() + self.var = dict() + self.valid = False + if config is not None and schema is not None: + self.load_conf(config) + self.load_schema(schema) + self.check_conf() + + def has(self, group, item=None): + if item is None: + return self.conf.has_section(group) + return self.conf.has_option(group, item) + + def set(self, group, item, value): + if not self.conf.has_section(group): + self.conf.add_section(group) + if self.log_changes: + log.info("config section added [{}]".format(group)) + if self.conf.has_option(group, item): + old_val = self.conf.get(group, item) + else: + old_val = None + if old_val != value: + self.conf.set(group, item, value) + if self.log_changes: + log.info("config option changed [{}]{}={} (from:{})".format(group, item, value, old_val)) + + def set_default(self, group, item_name): + self.set(group,item_name,str(self.get_default(group, item_name))) + + def get_var_origin(self, group_name, ): + # reverse keys alphabetically + keys = sorted(self.schema.keys(), reverse=True) + + for item in keys: + if "~" in item: + tok = item.split("~") + if len(tok) == 1: + item_prefix = tok[0] + item_postfix = "" + elif len(tok) == 2: + item_prefix = tok[0] + item_postfix = tok[1] + else: + return "" + + if group_name.startswith(item_prefix) and group_name.endswith(item_postfix): + return item + return "" + + def get_default(self, group, item_name): + group_og = self.get_var_origin(group) + + item = self.schema[group_og][item_name] + if "default" not in item.keys(): + return "" + item_type = item["type"] + if item_type == "string": + result =item["default"] + elif item_type == "int" or item_type == "long": + result = int(item["default"]) + elif item_type == "bool": + result = bool(item["default"]) + elif item_type == "float": + result = float(item["default"]) + elif item_type == "uri": + result = urlparse(item["default"]) + elif item_type == "list": + result = item["default"].split(",") + elif item_type == "path": + result = path.normpath(item["default"]) + elif item_type.startswith("template"): + tok = item_type.split(",") + if len(tok) > 1: + sub_type = tok[1] + else: + sub_type = "string" + result = Template(item["default"], sub_type) + + + return result + def get(self, group, item=None): + """ + Given a group and an item return its value + + Args: + group: str. group name + item: str. item name + Returns: + obj: an object containing the value + """ + if item is None: + # If user specified group.item together + if '.' in group: + tokens = group.split('.') + group = tokens[0] + item = tokens[1] + # If only group was specified + else: + result = {} + if group in self.fix: + result.update(self.fix[group]) + if group in self.var: + result.update(self.var[group]) + return result + + if '*' in item: + r = re.compile(item.replace('*', '.*')) + results = {} + if group in self.fix: + items = filter(r.match, self.fix[group].keys()) + for item in items: + results[item] = self.fix[group][item] + if group in self.var: + items = filter(r.match, self.var[group].keys()) + for item in items: + results[item] = self.var[group][item] + return results + + if group in self.fix: + if item in self.fix[group]: + if self.fix[group][item] is not None: + return self.fix[group][item]["value"] + if group in self.var: + if item in self.var[group]: + if self.var[group][item] is not None: + return self.var[group][item]["value"] + return None + + def load_conf(self, conf_path): + """ + Load configuration from file using a SafeConfigParser + """ + self.conf.read(conf_path) + self.conf_path = conf_path + + def load_schema(self, schema_path): + """ + Load configuration schema (JSON format) from file + """ + with open(schema_path, 'r') as schema_file: + self.schema = json.load(schema_file) + self.schema_path = schema_path + + def save_as(self, file_path): + with open(file_path, 'w') as file_conf: + self.conf.write(file_conf) + + def get_as(self, group, item, item_type, og_item): + """ + Return appropriate value dict object + + Args: + group: str. group name + item: str. item name + item_type: str. type of the item + og_item: str. optional reference to original item in schema + + + Returns: + dict: result dictionary with value and optional reference to original item in schema + """ + pack = dict() + + try: + result = None + if item_type == "string": + result = self.conf.get(group, item) + elif item_type == "int" or item_type == "long": + result = self.conf.getint(group, item) + elif item_type == "bool": + result = self.conf.getboolean(group, item) + elif item_type == "float": + result = self.conf.getfloat(group, item) + elif item_type == "uri": + result = urlparse(self.conf.get(group, item)) + elif item_type == "list": + result = self.conf.get(group, item).split(",") + elif item_type == "path": + result = path.normpath(self.conf.get(group, item)) + elif item_type.startswith("template"): + tok = item_type.split(",") + if len(tok) > 1: + sub_type = tok[1] + else: + sub_type = "string" + result = Template(self.conf.get(group, item), sub_type) + + pack["value"] = result + + if og_item != item: + pack["og_item"] = og_item + except Exception, e: + log.error("Not found [{}][{}]".format(group,item)) + self.valid = False + return + return pack + + def add_config_item(self, group, item, og_item, dest, og_group): + """ + Add new item to the ArgoConfig params + + Args: + group: str. group name + item: str. item name + og_item: str. reference to original item in schema + dest: dict. where to add the item (in the fixed or varied item dictionary) + og_group: str. reference to original group in schema + """ + if og_group is not None: + schema_group = og_group + else: + schema_group = group + + if "optional" in self.schema[schema_group][og_item].keys(): + if self.schema[schema_group][og_item]["optional"]: + if not self.conf.has_option(group, item): + return + + if group not in dest: + dest[group] = dict() + if og_group is not None: + dest[group]["og_group"] = og_group + + dest[group][item] = self.get_as(group, item, self.schema[schema_group][og_item]["type"], og_item) + + def add_group_items(self, group, items, var, og_group): + """ + Add a list of items to a group. If var=true the items are treated as + varied items. + + Args: + group: str. group name + items: list(str). list of item names + var: bool. if the items are considered varied + og_group: str. reference to original group in schema + """ + if var: + dest = self.var + else: + dest = self.fix + + for item in items: + if type(item) is not dict: + self.add_config_item(group, item, item, dest, og_group) + else: + for sub_item in item["vars"]: + self.add_config_item(group, sub_item, item["item"], dest, og_group) + + @staticmethod + def is_var(name): + """ + If a name is considered to represent a varied object" + + Returns: + bool. is considered varied or not + """ + if "~" in name: + return True + + return False + + def get_item_variations(self, group, item, ogroup): + """ + Search schema for the field that provides the variations + and create a list of the expected varied items + + Returns: + list(str). a list with all available item name variations + """ + variations = {'group': group, 'item': item, 'vars': list()} + + if ogroup is not None: + map_pool = self.schema[ogroup][item]["~"] + else: + map_pool = self.schema[group][item]["~"] + if '.' in map_pool: + map_pool = map_pool.split(".") + else: + tmp_item = map_pool + map_pool = list() + map_pool.append(group) + map_pool.append(tmp_item) + try: + name_pool = self.conf.get(map_pool[0], map_pool[1]).split(",") + if name_pool == [""]: + return None + except Exception, e: + log.error("Not found [{}]{}".format(map_pool[0],map_pool[1])) + self.valid=False + return None + for name in name_pool: + variations["vars"].append(item.replace("~", name)) + return variations + + def get_group_variations(self, group): + """ + Search schema for the field that provides the variations + and create a list of the expected varied groups + + Returns: + list(str). a list with all available group name variations + """ + variations = {'group': group, 'vars': list()} + + map_pool = self.schema[group]["~"] + if '.' in map_pool: + map_pool = map_pool.split(".") + else: + item = map_pool + map_pool = list() + map_pool.append(group) + map_pool.append(item) + + name_pool = self.conf.get(map_pool[0], map_pool[1]).split(",") + + if name_pool == [""]: + return None + + for name in name_pool: + variations["vars"].append(group.replace("~", name)) + return variations + + def check_conf(self): + + """ + Validate schema and configuration file. Iterate and extract + all configuration parameters + """ + self.valid = True + fix_groups = self.schema.keys() + var_groups = list() + + + for group in fix_groups: + if self.is_var(group): + + var_group = self.get_group_variations(group) + if var_group is not None: + var_groups.append(var_group) + + continue + + fix_items = list() + var_items = list() + for item in self.schema[group].keys(): + if self.is_var(item): + group_vars = self.get_item_variations(group,item,None) + if group_vars is not None: + var_items.append(self.get_item_variations(group, item, None)) + continue + fix_items.append(item) + self.add_group_items(group, fix_items, False, None) + self.add_group_items(group, var_items, True, None) + + + for group in var_groups: + + for sub_group in group["vars"]: + fix_items = list() + var_items = list() + for item in self.schema[group["group"]].keys(): + + if item == "~": + continue + + if self.is_var(item): + item_vars = self.get_item_variations(sub_group, item, group["group"]) + if item_vars is not None: + var_items.append(item_vars) + continue + fix_items.append(item) + # Both fix and var items are in a var group so are considered var + self.add_group_items(sub_group, fix_items, True, group["group"]) + self.add_group_items(sub_group, var_items, True, group["group"]) + + diff --git a/bin/utils/argo_log.py b/bin/utils/argo_log.py deleted file mode 100755 index 1ea9b064..00000000 --- a/bin/utils/argo_log.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python - -import sys -import logging -import logging.handlers - - -class ArgoLogger(logging.Logger): - - def __init__(self, log_name=None, config=None): - - if log_name is not None and config is not None: - # constructor of the super class Logger expects the name of the logger - # we pass it through super() - super(ArgoLogger, self).__init__(log_name) - - log_modes = config.get("LOGS", "log_modes").split(",") - log_level = config.get("LOGS", "log_level") - - levels = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL - } - - for log_mode in log_modes: - kwargs = {} - kwargs["log_mode"] = log_mode - kwargs["global_log_level"] = log_level - kwargs["config"] = config - kwargs["levels"] = levels - if log_mode == "console": - self.add_stream_handler(**kwargs) - elif log_mode == "syslog": - self.add_syslog_handler(**kwargs) - elif log_mode == "file": - self.add_file_handler(**kwargs) - else: - # set up a test logger whenever running tests - super(ArgoLogger, self).__init__("test") - stream_log = logging.StreamHandler() - stream_log.setLevel(logging.INFO) - self.addHandler(stream_log) - - def print_and_log(self, level, message, exit_code=None): - """ Logger method that prints the logging message aswell - Attributes: - self(ArgoLogger): an instance of the ArgoLogger class - messsage(str): message to be logged, aswell printed, depending on stream handler's level - level(int): the level of the log message - exit_code(int): if specified, the programm will exit with the specified code - """ - self.log(level, message) - - if exit_code is not None: - sys.exit(exit_code) - - def config_str_validator(self, config, section, option): - """ A method to check whether or not a config string option is not only present, but has a value aswell. It also checks the existance of the section""" - if config.has_section(section): - if config.has_option(section, option): - if len(config.get(section, option)) != 0: - return True - self.print_and_log(logging.CRITICAL, "Section: "+section+" with option: "+option+" was not found in the conf file, or had no value.", 1) - - def add_stream_handler(self, **kwargs): - """Method that takes care setting up a StreamHandler properly""" - - levels = kwargs["levels"] - - stream_handler = logging.StreamHandler() - - if self.config_str_validator(kwargs["config"], "LOGS", "console_level"): - stream_handler.setLevel(levels[kwargs["config"].get("LOGS", "console_level").upper()]) - else: - stream_handler.setLevel(levels[kwargs["global_log_level"].upper()]) - - self.addHandler(stream_handler) - - def add_syslog_handler(self, **kwargs): - """Method that take cares setting up a Syslog Handler properly. Making sure socket and level are configured properly""" - - levels = kwargs["levels"] - - if self.config_str_validator(kwargs["config"], "LOGS", "syslog_socket"): - sys_log = logging.handlers.SysLogHandler(kwargs["config"].get("LOGS", "syslog_socket")) - else: - raise TypeError("No socket specified for syslog handler") - - if self.config_str_validator(kwargs["config"], "LOGS", "syslog_level"): - sys_log.setLevel(levels[kwargs["config"].get("LOGS", "syslog_level").upper()]) - else: - sys_log.setLevel(levels[kwargs["global_log_level"].upper()]) - - sys_log.setFormatter(logging.Formatter('%(name)s[%(process)d]: %(levelname)s %(message)s')) - - self.addHandler(sys_log) - - def add_file_handler(self, **kwargs): - """ Method that takes care setting up a FileHandler properly. Making sure that the path to save file and level are configured properly""" - - levels = kwargs["levels"] - - if self.config_str_validator(kwargs["config"], "LOGS", "file_path"): - file_log = logging.FileHandler(kwargs["config"].get("LOGS", "file_path")) - else: - raise TypeError("No filepath specified for file handler") - - if self.config_str_validator(kwargs["config"], "LOGS", "file_level"): - file_log.setLevel(levels[kwargs["config"].get("LOGS", "file_level").upper()]) - else: - file_log.setLevel(levels[kwargs["global_log_level"].upper()]) - - file_log.setFormatter(logging.Formatter('%(asctime)s %(name)s[%(process)d]: %(levelname)s %(message)s')) - - self.addHandler(file_log) diff --git a/bin/utils/argo_mongo.py b/bin/utils/argo_mongo.py index 31183d87..ed3cbb8f 100755 --- a/bin/utils/argo_mongo.py +++ b/bin/utils/argo_mongo.py @@ -1,214 +1,188 @@ #!/usr/bin/env python import sys -import os import logging import argparse -import ConfigParser import pymongo from pymongo import MongoClient -from argo_log import ArgoLogger -from urlparse import urlsplit +from pymongo.errors import ServerSelectionTimeoutError +from argo_config import ArgoConfig +from common import get_config_paths +from common import get_log_conf + +log = logging.getLogger(__name__) class ArgoMongoClient(object): - def __init__(self, args, config, logger, cols): + def __init__(self, args, config, cols): self.args = args self.config = config - self.logger = logger self.cols = cols - def mongo_clean_ar(self, uri): - # whenever a report is specified, check if the tenant supports such report, else the programm will exist from the str_validator method - if self.args.Report and self.logger.config_str_validator(self.config, "TENANTS:"+self.args.Tenant+":REPORTS", self.args.Report): - tenant_report = self.config.get("TENANTS:"+self.args.Tenant+":REPORTS", self.args.Report) + tenant_report = None + + # if report is given check if report exists in configuration + if self.args.report: + report_name = self.args.report + tenant_group = "TENANTS:" + self.args.tenant + if report_name in self.config.get(tenant_group, "reports"): + tenant_report = self.config.get(tenant_group, "report_"+report_name) + else: + log.critical("Report %s not found", report_name) + sys.exit(1) # Create a date integer for use in the database queries - date_int = int(self.args.Date.replace("-", "")) - + date_int = int(self.args.date.replace("-", "")) + # set up the mongo client try: - self.logger.print_and_log(logging.INFO, "Trying to connect to: "+uri) - client = MongoClient(uri) + log.info("Trying to connect to: " + uri.geturl()) + client = MongoClient(uri.geturl()) # force a connection to test the client client.server_info() - except pymongo.errors.ServerSelectionTimeoutError as pse: - self.logger.print_and_log(logging.CRITICAL,str(pse)+ ". Make sure mongo daemon is up and running. Programm will now exit ...", 1) + except ServerSelectionTimeoutError as pse: + log.fatal(pse) + sys.exit(1) # specify the db we will be using. e.g argo_TENANTA # from the uri, take the path, which reprents the db, and ignore the / in the begging - db = client[urlsplit(uri).path[1:]] - + db = client[uri.path[1:]] + # iterate over the specified collections for col in self.cols: - if self.args.Report: + if tenant_report is not None: num_of_rows = db[col].find({"date": date_int, "report": tenant_report}).count() - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Found " +str(num_of_rows)+" entries for date: "+self.args.Date+" and report: " +self.args.Report) + log.info("Collection: " + col + " -> Found " + str( + num_of_rows) + " entries for date: " + self.args.date + " and report: " + self.args.report) else: num_of_rows = db[col].find({"date": date_int}).count() - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Found " +str(num_of_rows)+" entries for date: "+self.args.Date+". No report specified!") + log.info("Collection: " + col + " -> Found " + str( + num_of_rows) + " entries for date: " + self.args.date + ". No report specified!") if num_of_rows > 0: - if self.args.Report: + if tenant_report is not None: # response returned from the delete operation res = db[col].delete_many({"date": date_int, "report": tenant_report}) - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Removed " +str(res.deleted_count)+" entries for date: "+self.args.Date+" and report: " +self.args.Report) + log.info("Collection: " + col + " -> Removed " + str(res.deleted_count) + + " entries for date: " + self.args.date + " and report: " + self.args.report) else: # response returned from the delete operation res = db[col].delete_many({"date": date_int, "report": tenant_report}) - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Removed " +str(res.deleted_count)+" entries for date: "+self.args.Date+". No report specified!") - self.logger.print_and_log(logging.INFO, "Entries removed successfully") + log.info("Collection: " + col + " -> Removed " + str( + res.deleted_count) + " entries for date: " + self.args.date + ". No report specified!") + log.info("Entries removed successfully") else: - self.logger.print_and_log(logging.INFO, "Zero entries found. Nothing to remove.") - + log.info("Zero entries found. Nothing to remove.") + # close the connection with mongo client.close() - + def mongo_clean_status(self, uri): - - # whenever a report is specified, check if the tenant supports such report, else the programm will exist from the str_validator method - if self.args.Report and self.logger.config_str_validator(self.config, "TENANTS:"+self.args.Tenant+":REPORTS", self.args.Report): - tenant_report = self.config.get("TENANTS:"+self.args.Tenant+":REPORTS", self.args.Report) + + tenant_report = None + + # if report is given check if report exists in configuration + if self.args.report: + report_name = self.args.report + tenant_group = "TENANTS:" + self.args.tenant + if report_name in self.config.get(tenant_group, "reports"): + tenant_report = self.config.get(tenant_group, "report_"+report_name) + else: + log.critical("Report %s not found", report_name) + sys.exit(1) # Create a date integer for use in the database queries - date_int = int(self.args.Date.replace("-", "")) - + date_int = int(self.args.date.replace("-", "")) + # set up the mongo client try: - self.logger.print_and_log(logging.INFO, "Trying to connect to: "+uri) - client = MongoClient(uri) + log.info("Trying to connect to: " + uri.geturl()) + client = MongoClient(uri.geturl()) # force a connection to test the client client.server_info() except pymongo.errors.ServerSelectionTimeoutError as pse: - self.logger.print_and_log(logging.CRITICAL,str(pse)+ ". Make sure mongo daemon is up and running. Programm will now exit ...", 1) + log.fatal(pse) + sys.exit(1) # specify the db we will be using. e.g argo_TENANTA # from the uri, retrieve the path section, which reprents the db, and ignore the / in the begging - db = client[urlsplit(uri).path[1:]] - + db = client[uri.path[1:]] + # iterate over the specified collections for col in self.cols: - if self.args.Report: + if tenant_report is not None: num_of_rows = db[col].find({"date_integer": date_int, "report": tenant_report}).count() - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Found " +str(num_of_rows)+" entries for date: "+self.args.Date+" and report: " +self.args.Report) + log.info("Collection: " + col + " -> Found " + str( + num_of_rows) + " entries for date: " + self.args.date + " and report: " + self.args.report) else: - num_of_rows = db[col].find({"date_integer": date_int}).count() - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Found " +str(num_of_rows)+" entries for date: "+self.args.Date+". No report specified!") + num_of_rows = db[col].find({"date": date_int}).count() + log.info("Collection: " + col + " -> Found " + str( + num_of_rows) + " entries for date: " + self.args.date + ". No report specified!") if num_of_rows > 0: - if self.args.Report: + if tenant_report is not None: # response returned from the delete operation res = db[col].delete_many({"date_integer": date_int, "report": tenant_report}) - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Removed " +str(res.deleted_count)+" entries for date: "+self.args.Date+" and report: " +self.args.Report) + log.info("Collection: " + col + " -> Removed " + str(res.deleted_count) + + " entries for date: " + self.args.date + " and report: " + self.args.report) else: # response returned from the delete operation res = db[col].delete_many({"date_integer": date_int, "report": tenant_report}) - self.logger.print_and_log(logging.WARNING, "Collection: "+col+" -> Removed " +str(res.deleted_count)+" entries for date: "+self.args.Date+". No report specified!") - self.logger.print_and_log(logging.INFO, "Entries removed successfully") + log.info("Collection: " + col + " -> Removed " + str( + res.deleted_count) + " entries for date: " + self.args.Date + ". No report specified!") + log.info("Entries removed successfully") else: - self.logger.print_and_log(logging.INFO, "Zero entries found. Nothing to remove.") + log.info("Zero entries found. Nothing to remove.") # close the connection with mongo client.close() - -def main_clean_ar(args=None): - # stand alone method to be used whenever we want to call the mongo_clean_ar method independently - - # make sure the argument are in the correct form - args.Report = args.Report.capitalize() - args.Tenant = args.Tenant.upper() - - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../../conf/conf.cfg") - else: - config.read(args.ConfigPath) - - # set up the logger - logger = ArgoLogger(log_name="batch-ar", config=config) - - # check if configuration for the given tenant exists - if not config.has_section("TENANTS:"+args.Tenant): - logger.print_and_log(logging.CRITICAL, "Tenant: "+args.Tenant+" doesn't exist.", 1) - - # set up the mongo uri - mongo_tenant = "TENANTS:"+args.Tenant+":MONGO" - mongo_uri = config.get(mongo_tenant, "mongo_uri") - mongo_uri = mongo_uri.replace("{{mongo_host}}", config.get(mongo_tenant, "mongo_host")) - mongo_uri = mongo_uri.replace("{{mongo_port}}", config.get(mongo_tenant, "mongo_port")) - - argo_mongo_client = ArgoMongoClient(args, config, logger, ["service_ar","endpoint_group_ar"]) - argo_mongo_client.mongo_clean_ar(mongo_uri) - -def main_clean_status(args=None): + + +def main_clean(args=None): # stand alone method to be used whenever we want to call the mongo_clean_status method independently - # make sure the argument are in the correct form - args.Report = args.Report.capitalize() - args.Tenant = args.Tenant.upper() - - # set up the config parser - config = ConfigParser.ConfigParser() - - # check if config file has been given as cli argument else - # check if config file resides in /etc/argo-streaming/ folder else - # check if config file resides in local folder - if args.ConfigPath is None: - if os.path.isfile("/etc/argo-streaming/conf/conf.cfg"): - config.read("/etc/argo-streaming/conf/conf.cfg") - else: - config.read("../../conf/conf.cfg") - else: - config.read(args.ConfigPath) - - # set up the logger - logger = ArgoLogger(log_name="batch-ar", config=config) - - # check if configuration for the given tenant exists - if not config.has_section("TENANTS:"+args.Tenant): - logger.print_and_log(logging.CRITICAL, "Tenant: "+args.Tenant+" doesn't exist.", 1) - - # set up the mongo uri - mongo_tenant = "TENANTS:"+args.Tenant+":MONGO" - mongo_uri = config.get(mongo_tenant, "mongo_uri") - mongo_uri = mongo_uri.replace("{{mongo_host}}", config.get(mongo_tenant, "mongo_host")) - mongo_uri = mongo_uri.replace("{{mongo_port}}", config.get(mongo_tenant, "mongo_port")) - - argo_mongo_client = ArgoMongoClient(args, config, logger, ["status_metrics","status_endpoints","status_services","status_endpoint_groups"]) - argo_mongo_client.mongo_clean_status(mongo_uri) - -# Provide the ability to the script, to be runned as a standalone module + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + + # set up the mongo uri + section_tenant = "TENANTS:" + args.tenant + mongo_endpoint = config.get("MONGO","endpoint") + mongo_uri = config.get(section_tenant,"mongo_uri").fill(mongo_endpoint=mongo_endpoint,tenant=args.tenant) + + + if args.job == "clean_ar": + argo_mongo_client = ArgoMongoClient(args, config, ["service_ar", "endpoint_group_ar"]) + argo_mongo_client.mongo_clean_ar(mongo_uri) + + elif args.job == "clean_status": + argo_mongo_client = ArgoMongoClient(args, config, ["status_metrics", "status_endpoints", "status_services", + "status_endpoint_groups"]) + argo_mongo_client.mongo_clean_status(mongo_uri) + + +# Provide the ability to the script, to run as a standalone module if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Mongo clean up script") parser.add_argument( - "-t", "--Tenant", type=str, help="Name of the tenant", required=True) + "-t", "--tenant", metavar="STRING", help="Name of the tenant", required=True, dest="tenant") parser.add_argument( - "-r", "--Report", type=str, help="Report status", required=True) + "-r", "--report", metavar="STRING", help="Tenant report to be used", required=True, dest="report") parser.add_argument( - "-d", "--Date", type=str, help="Date to run the job for", required=True) + "-d", "--date", metavar="STRING", help="Date to run the job for", required=True, dest="date") parser.add_argument( - "-c", "--ConfigPath", type=str, help="Path for the config file") + "-c", "--config", metavar="STRING", help="Path for the config file", dest="config") parser.add_argument( - "-j", "--Job", type=str, help="Stand alone method we wish to run", required=True) + "-j", "--job", metavar="STRING", help="Stand alone method we wish to run", required=True, dest="job") # Parse the arguments - args = parser.parse_args() - - # pass them to the respective main method - if args.Job == "clean_ar": - sys.exit(main_clean_ar(args)) - elif args.Job == "clean_status": - sys.exit(main_clean_status(args)) + sys.exit(main_clean(parser.parse_args())) diff --git a/bin/utils/check_tenant.py b/bin/utils/check_tenant.py new file mode 100755 index 00000000..1d1b9f3d --- /dev/null +++ b/bin/utils/check_tenant.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +from argparse import ArgumentParser +from common import get_config_paths, get_log_conf +from argo_config import ArgoConfig +import sys +import logging +import json +from snakebite.client import Client +from datetime import datetime, timedelta +from update_ams import ArgoAmsClient +import requests + + + +log = logging.getLogger(__name__) + + +def get_today(): + """Get todays date in YYYY-MM-DD format + + Returns: + str.: today's date in YYYY-MM-DD format + """ + + return datetime.today().strftime('%Y-%m-%d') + +def get_date_days_back(date_str, days_back): + """Get date, x days back from target date, as a string in YYYY-MM-DD format + + Args: + date_str (str.): target date in string YYYY-MM-DD format + days_back (int): days to go back + + Returns: + str.: date in YYYY-MM-DD format + """ + + if days_back is 0: + return date_str + target_date = datetime.strptime(date_str,'%Y-%m-%d') - timedelta(days=days_back) + return target_date.strftime('%Y-%m-%d') + + +def get_now_iso(): + """Get current utc datetime in YYYY-MM-DDTHH:MM:SSZ format + + Returns: + str.: current datetime in YYY-MM-DDTHH:MM:SSZ format + """ + + return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + + +def check_tenant_hdfs(tenant, target_date, days_back, namenode, hdfs_user, client, config): + """Given a tenant and an hdfs client check if the tenant is properly configured + in the hdfs destination. + + Args: + tenant (str.): tenant's name + target_date (str.): YYYY-MM-DD date string + namenode (str.): url of hdfs namenode + hdfs_user (str.): name of hdfs user + client (obj.): hdfs snakebite client + config (obj.): argo config object + + Returns: + dict.: json representation of the hdfs status check + """ + + + # check hdfs metric data + hdfs_metric = config.get("HDFS","path_metric").fill(namenode=namenode.geturl(),user=hdfs_user,tenant=tenant) + hdfs_status = dict() + metric_path = "".join([hdfs_metric.path,"/",target_date,"/*"]) + count = 0 + + try: + for _ in client.count([metric_path]): + count = count + 1 + except Exception: + log.error("Error on hdfs location for tenant {}".format(tenant)) + # append hdfs metric check results in hdfs category + if count > 0: + hdfs_status["metric_data"] = True + else: + hdfs_status["metric_data"] = False + + # check hdfs sync data + hdfs_sync= config.get("HDFS","path_sync").fill(namenode=namenode.geturl(),user=hdfs_user,tenant=tenant) + sync_list = ["metric_profile", "group_groups", "group_endpoints", "weights", "downtimes"] + report_profiles = {"configuration_profile": "{}_{}_cfg.json", + "aggregation_profile": "{}_{}_ap.json", + "operations_profile": "{}_ops.json", + "blank_recomputation": "recomp.json" + } + reports = config.get("TENANTS:{}".format(tenant),"reports") + + sync_result = dict() + for report in reports: + sync_result[report]={} + for item in sync_list: + # Repeat check for how many days back user specified + for days_iter in range(0,days_back): + check_date = get_date_days_back(target_date,days_iter) + sync_path = "".join([hdfs_sync.path,"/",report,"/",item,"_",check_date,".avro"]) + try: + client.test(sync_path) + # If found set status to True and escape back day check + sync_result[report][item] = True + continue + except Exception: + sync_result[report][item] = False + + for item in report_profiles.keys(): + profile_path = "".join([hdfs_sync.path,"/",report_profiles[item].format(tenant,report)]) + try: + client.test(profile_path) + sync_result[report][item] = True + except Exception: + sync_result[report][item] = False + # append hdfs sync check results in hdfs category + hdfs_status["sync_data"] = sync_result + # append current tenant to the list of tenants in general status report + return hdfs_status + + +def check_tenant_ams(tenant, target_date, ams, config): + """Given a tenant and ams client check if the tenant is properly configured + in remote ams + + Args: + tenant (str.): tenant's name + target_date (str.): YYYY-MM-DD date string + ams (obj.): ams client handling connection to a remote ams endpoint + config (obj.): argo configuration object + + Returns: + dict.: json string representing tenant's ams status check + """ + + # check ams + ams_tenant = { + "metric_data": { + "publishing": False, + "ingestion":False, + "status_streaming": False, + "messages_arrived": 0, + }, + "sync_data": { + "publishing": False, + "ingestion":False, + "status_streaming": False, + "messages_arrived": 0, + } + } + + if ams.check_project_exists(tenant): + + tenant_topics = ams.get_tenant_topics(tenant) + topic_types = tenant_topics.keys() + if "metric_data" in topic_types: + ams_tenant["metric_data"]["publishing"] = True + if "sync_data" in topic_types: + ams_tenant["sync_data"]["publishing"] = True + + sub_types = ams.get_tenant_subs(tenant,tenant_topics).keys() + if "ingest_metric" in sub_types: + ams_tenant["metric_data"]["ingestion"] = True + if "status_metric" in sub_types: + ams_tenant["metric_data"]["status_streaming"] = True + if "ingest_sync" in sub_types: + ams_tenant["sync_data"]["ingestion"] = True + if "status_sync" in sub_types: + ams_tenant["sync_data"]["status_streaming"] = True + + ams_tenant["metric_data"]["messages_arrived"] = ams.get_topic_num_of_messages(tenant,"metric_data") + ams_tenant["sync_data"]["messages_arrived"] = ams.get_topic_num_of_messages(tenant,"sync_data") + + return ams_tenant + + +def check_tenants(tenants, target_date, days_back, config): + """Gets a list of tenants, a target date and number of days to go back and + checks for each tenant its hdfs and ams status. The status is uploaded in argo-web-api. + a complete list of tenant statuses is returned in the end + + Args: + tenants (list(str.)): List of tenant names + target_date (str.): target date in YYYY-MM-DD format + days_back (int): days to check back + config (obj.): ArgoConfig object + + Returns: + list(obj): List of tenants' statuses + """ + + # hdfs client init + namenode = config.get("HDFS","namenode") + hdfs_user = config.get("HDFS","user") + client = Client(namenode.hostname, namenode.port) + log.info("connecting to HDFS: {}".format(namenode.hostname)) + + # ams client init + ams_token = config.get("AMS", "access_token") + ams_host = config.get("AMS", "endpoint").hostname + ams = ArgoAmsClient(ams_host, ams_token) + log.info("connecting to AMS: {}".format(ams_host)) + + # Upload tenant statuses in argo web api + api_endpoint = config.get("API","endpoint").netloc + api_token = config.get("API","access_token") + + # Get tenant uuids + tenant_uuids = get_tenant_uuids(api_endpoint, api_token) + if not tenant_uuids: + log.error("Without tenant uuids service is unable to check and upload tenant status") + sys.exit(1) + + complete_status = list() + for tenant in tenants: + status_tenant = {} + + # add tenant name + status_tenant["tenant"] = tenant + # add check timestamp in UTC + status_tenant["last_check"] = get_now_iso() + # add engine_config category + status_tenant["engine_config"] = config.valid + # get hdfs status + status_tenant["hdfs"] = check_tenant_hdfs(tenant,target_date,days_back,namenode,hdfs_user,client,config) + # get ams status + status_tenant["ams"] = check_tenant_ams(tenant,target_date,ams,config) + + log.info("Status for tenant[{}] = {}".format(tenant,json.dumps(status_tenant))) + # Upload tenant status to argo-web-api + complete_status.append(status_tenant) + upload_tenant_status(api_endpoint,api_token,tenant,tenant_uuids[tenant],status_tenant) + + return complete_status + +def run_tenant_check(args): + """Run tenant/s check routine and update status json to argo-web-api + + Args: + args (obj): command line arguments + """ + + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + + + # if argo_engine configuration is invalid return + if config.valid is False: + log.error("Argo engine not properly configured check file:{}".format(conf_paths["main"])) + sys.exit(1) + + # check for specific date or today + if args.date is not None: + target_date = args.date + else: + target_date = get_today() + + # check for specific tenant or all of them + tenants = config.get("API","tenants") + if args.tenant is not None: + if args.tenant in tenants: + tenants = [args.tenant] + else: + log.error("tenant {} not found".format(args.tenant)) + return + + check_tenants(tenants,target_date,args.days_back,config) + + + + +def get_tenant_uuids(api_endpoint, api_token): + """Get tenant uuids from remote argo-web-api endpoint + + Args: + api_endpoint (str.): hostname of the remote argo-web-api endpoint + api_token (str.): access token for the remote argo-web-api endpoint + + Returns: + dict.: dictionary with mappings of tenant names to tenant uuidss + """ + + log.info("Retrieving tenant uuids from api: {}".format(api_endpoint)) + result = dict() + url = "https://{}/api/v2/admin/tenants".format(api_endpoint) + headers = dict() + headers.update({ + 'Accept': 'application/json', + 'x-api-key': api_token + }) + r = requests.get(url, headers=headers, verify=False) + + if 200 == r.status_code: + + tenants = json.loads(r.text)["data"] + for tenant in tenants: + result[tenant["info"]["name"]] = tenant["id"] + log.info("tenant uuids retrieved") + return result + else: + log.error("unable to retrieve tenant uuids") + return result + + +def upload_tenant_status(api_endpoint, api_token, tenant, tenant_id, tenant_status): + """Uploads tenant's status to a remote argo-web-api endpoint + + Args: + api_endpoint (str.): hostname of the remote argo-web-api endpoint + api_token (str.): access token for remote argo-web-api + tenant (str.): tenant name + tenant_id (str.): tenant uuid + tenant_status (obj.): json representation of tenant's status report + + Returns: + bool: true if upload is successfull + """ + + log.info("Uploading status for tenant: {}({}) at api: {}".format(tenant,tenant_id,api_endpoint)) + url = "https://{}/api/v2/admin/tenants/{}/status".format(api_endpoint,tenant_id) + headers = dict() + headers.update({ + 'Accept': 'application/json', + 'x-api-key': api_token + }) + r = requests.put(url, headers=headers, data=json.dumps(tenant_status), verify=False) + if 200 == r.status_code: + log.info("Tenant's {} status upload succesfull to {}".format(tenant, api_endpoint)) + return True + else: + log.error("Error uploading to the api {}, status_code: {} - response: {}".format(api_endpoint,r.status_code, r.text)) + return False + + + + +if __name__ == '__main__': + # Feed Argument parser with the description of the 3 arguments we need + arg_parser = ArgumentParser( + description="check status of tenant") + arg_parser.add_argument( + "-t", "--tenant", help="tenant owner ", dest="tenant", metavar="STRING", required=False, default=None) + arg_parser.add_argument( + "-c", "--config", help="config", dest="config", metavar="PATH") + arg_parser.add_argument( + "-d", "--date", help="date", dest="date", metavar="YYYY-MM-DD") + arg_parser.add_argument( + "-b", "--back-days", help="number", dest="days_back", metavar="INTEGER", default=3) + + # Parse the command line arguments accordingly and introduce them to the run method + sys.exit(run_tenant_check(arg_parser.parse_args())) diff --git a/bin/utils/common.py b/bin/utils/common.py index c2ed3966..bd8b4fcf 100644 --- a/bin/utils/common.py +++ b/bin/utils/common.py @@ -1,13 +1,19 @@ import datetime -import logging import requests import json import subprocess from subprocess import check_call from urlparse import urlparse, urlsplit, urlunsplit +import logging.config +import logging +import os.path +import sys + +log = logging.getLogger(__name__) -def cmd_toString(cmd): + +def cmd_to_string(cmd): """ Take as input a list containing the job sumbission command and return a string representation Attributes: @@ -16,20 +22,20 @@ def cmd_toString(cmd): (str): String representation of the submition command """ - return " ".join(x for x in cmd) + return " ".join(str(x) for x in cmd) -def date_rollback(path, year, month, day, config, logger, client): +def date_rollback(path, year, month, day, config, client): """Method that checks if a file(inside the hdfs) that is described by a date exists. - If it doesn't exist, it will rollback the date up to a number days specified by the conf file and see if the file exists - for any of the dates.If a file is not found it will exit with the appropriate message. + If it doesn't exist, it will rollback the date up to a number days specified by the conf file + and see if the file exists for any of the dates. + If a file is not found it will exit with the appropriate message. Attributes: path(str): the hdfs filepath year(int): the year of the starting date month(int): the month of the starting date day(int) : the day of the starting date config(ConfigParser): script's configuration - logger(ArgoLogger): logger client(snakebite.Client): Client for hdfs Returns: (str): the hdfs file path of the latest date @@ -37,25 +43,28 @@ def date_rollback(path, year, month, day, config, logger, client): snakebite_path = urlparse(path).path # maximun number of days it should rollback - days = config.getint("HDFS", "rollback_days") - - # number of day to check - day_to_check = 0 + days = config.get("HDFS", "rollback_days") starting_date = datetime.date(year, month, day) if client.test(snakebite_path.replace("{{date}}", str(starting_date)), exists=True): return path.replace("{{date}}", str(starting_date)) else: - logger.print_and_log(logging.WARNING, "- File : "+snakebite_path.replace("{{date}}", str(starting_date))+" was not found. Beggining rollback proccess for: "+str(days)+" days") + log.warn("File : " + snakebite_path.replace("{{date}}", str( + starting_date)) + " was not found. Beggining rollback proccess for: " + str(days) + " days") day_to_check = 1 - # while the number of maximum days we can rollback is greater than the number of days we have already rolledback, continue searching + # while the number of maximum days we can rollback is greater than the number of days we have already + # rolledback, continue searching while days >= day_to_check: - logger.print_and_log(logging.WARNING, "Trying: "+snakebite_path.replace("{{date}}", str(starting_date-datetime.timedelta(day_to_check)))) - if client.test(snakebite_path.replace("{{date}}", str(starting_date-datetime.timedelta(day_to_check))), exists=True): - logger.print_and_log(logging.WARNING, "File found after: "+str(day_to_check)+" days.\n") - snakebite_path = snakebite_path.replace("{{date}}", str(starting_date-datetime.timedelta(day_to_check))) - # decompose the original path into a list, where scheme, netlock, path, query, fragment are the list indices from 0 to 4 respectively + log.warn( + "Trying: " + snakebite_path.replace("{{date}}", str(starting_date - datetime.timedelta(day_to_check)))) + if client.test(snakebite_path.replace("{{date}}", str(starting_date - datetime.timedelta(day_to_check))), + exists=True): + log.warn("File found after: " + str(day_to_check) + " days.\n") + snakebite_path = snakebite_path.replace("{{date}}", + str(starting_date - datetime.timedelta(day_to_check))) + # decompose the original path into a list, where scheme, netlock, path, query, + # fragment are the list indices from 0 to 4 respectively path_decomposed = list(urlsplit(path)) # update the path with the snakebite path containg the date the file was found path_decomposed[2] = snakebite_path @@ -65,41 +74,102 @@ def date_rollback(path, year, month, day, config, logger, client): # if the file was not found, rollback one more day day_to_check += 1 - logger.print_and_log(logging.INFO, "No file found after "+str(day_to_check-1)+" days.", 1) + log.critical("No file found after "+str(day_to_check-1)+" days.") + sys.exit(1) -def flink_job_submit(config, logger, cmd_command, job_namespace=None): +def flink_job_submit(config, cmd_command, job_namespace=None): """Method that takes a command and executes it, after checking for flink being up and running. If the job_namespace is defined, then it will also check for the specific job if its already running. If flink is not running or the job is already submitted, it will execute. Attributes: config(ConfigParser): script's configuration - logger(ArgoLogger): logger cmd_command(list): list contaning the command to be submitted job_namespace(string): the job's name """ # check if flink is up and running try: - flink_response = requests.get(config.get("FLINK", "job_manager")+"/joboverview/running") + flink_response = requests.get(config.get("FLINK", "job_manager").geturl()+"/joboverview/running") if job_namespace is not None: # if the job's already running then exit, else sumbit the command for job in json.loads(flink_response.text)["jobs"]: if job["name"] == job_namespace: - logger.print_and_log(logging.CRITICAL, "\nJob: "+"'"+job_namespace+"' is already running", 1) + log.critical("Job: "+"'"+job_namespace+"' is already running") + sys.exit(1) - logger.print_and_log(logging.INFO, "Everything is ok") + log.info("Everything is ok") try: check_call(cmd_command) except subprocess.CalledProcessError as esp: - logger.print_and_log(logging.CRITICAL, "Job was not submitted. Error exit code: "+str(esp.returncode), 1) + log.fatal("Job was not submitted. Error exit code: "+str(esp.returncode)) + sys.exit(1) except requests.exceptions.ConnectionError: - logger.print_and_log(logging.CRITICAL, "Flink is not currently running. Tried to communicate with job manager at: " + config.get("FLINK", "job_manager"), 1) - - -def hdfs_check_path(path, logger, client): - """Method that checks if a path in hdfs exists. If it exists it will return the path, else it will exit the script""" - if client.test(urlparse(path).path, exists=True): - return path - logger.print_and_log(logging.CRITICAL, "- File: "+path+" doesn't exist.", 1) + log.fatal("Flink is not currently running. Tried to communicate with job manager at: " + + config.get("FLINK", "job_manager").geturl()) + sys.exit(1) + + +def hdfs_check_path(uri, client): + """Method that checks if a path in hdfs exists. If it exists it will return the path, + else it will exit the script""" + if client.test(urlparse(uri).path, exists=True): + return uri + log.critical("File: " + uri + " doesn't exist.") + sys.exit(1) + + +def get_config_paths(config_file=None): + + def check_paths(path_directory, main_filename=None): + std_names = ['argo-streaming.conf', 'config.schema.json', 'logger.conf'] + path_list = [] + if main_filename is not None: + std_names[0] = main_filename + + for name in std_names: + full_path = os.path.join(path_directory, name) + if not os.path.exists(os.path.join(path_directory, name)): + return None + path_list.append(full_path) + + # we need three paths + if len(path_list) == 3: + return {'main': path_list[0], 'schema': path_list[1], 'log': path_list[2]} + else: + return None + + # check if configuration file was given as an argument + if config_file: + path_dir = os.path.dirname(config_file) + config_paths = check_paths(path_dir, os.path.basename(config_file)) + if config_paths is not None: + return config_paths + + # then check in local setup + path_dir = os.path.abspath('../../conf') + config_paths = check_paths(path_dir) + if config_paths is not None: + return config_paths + + # lastly check in /etc/ + path_dir = os.path.abspath('/etc/argo-streaming/') + config_paths = check_paths(path_dir) + if config_paths is not None: + return config_paths + + # lastly display error -- logger wont be configured yet + get_log_conf() + log.fatal('No configuration found in arguments, local conf folder or /etc/argo-streaming/') + raise RuntimeError("no configuration found") + + +def get_log_conf(log_config_file=None): + """ + Method that searches and gets the default location of configuration and logging configuration + """ + if log_config_file is not None: + logging.config.fileConfig(log_config_file, disable_existing_loggers=False) + else: + logging.basicConfig(level=logging.INFO, format=logging.BASIC_FORMAT) diff --git a/bin/utils/recomputations.py b/bin/utils/recomputations.py new file mode 100755 index 00000000..b98a9bcd --- /dev/null +++ b/bin/utils/recomputations.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +import os +import sys +import json +from argparse import ArgumentParser +from pymongo import MongoClient +from bson import json_util +import logging +from common import get_config_paths +from common import get_log_conf +from argo_config import ArgoConfig +import subprocess + + +log = logging.getLogger(__name__) + +def write_output(results, tenant, report, target_date, config): + """Write recomputation output to hdfs + + Args: + results (list(obj)): List of recomputation definitions + tenant (str.): tenant name + report (str.): report name + target_date ([type]): target date + config ([type]): argo configuration object + + Returns: + bool: False if upload had errors + """ + + if len(results) == 0: + log.info("No recomputations found skipping") + return True + # create a temporary recalculation file in the ar-sync folder + recomp_name = "".join(["recomp", "_", tenant, "_", report, "_", target_date, ".json"]) + recomp_filepath = os.path.join("/tmp/", recomp_name) + + # write output file to the correct job path + with open(recomp_filepath, 'w') as output_file: + json.dump(results, output_file, default=json_util.default) + + # upload file to hdfs + hdfs_writer = config.get("HDFS", "writer_bin") + hdfs_namenode = config.get("HDFS", "namenode") + hdfs_user = config.get("HDFS", "user") + hdfs_sync = config.get("HDFS", "path_sync").fill(namenode=hdfs_namenode.geturl(), hdfs_user=hdfs_user, tenant=tenant).geturl() + print type(hdfs_sync), hdfs_sync + status = subprocess.check_call([hdfs_writer, "put", recomp_filepath, hdfs_sync]) + # clear temp local file + os.remove(recomp_filepath) + if status == 0: + log.info("File uploaded successfully to hdfs: %s", hdfs_sync ) + return True + else: + log.error("File uploaded unsuccessful to hdfs: %s", hdfs_sync) + return False + + +def get_mongo_collection(mongo_uri, collection): + """Return a pymongo collection object from a collection name + + Args: + mongo_uri (obj.): mongodb uri + collection (str.): collection name + + Returns: + obj.: pymongo collection object + """ + + log.info ("Connecting to mongodb: %s", mongo_uri.geturl()) + print mongo_uri.geturl() + client = MongoClient(mongo_uri.geturl()) + log.info("Opening database: %s", mongo_uri.path[1:]) + db = client[mongo_uri.path[1:]] + log.info("Opening collection: %s", collection) + col = db[collection] + + return col + + +def get_mongo_results(collection, target_date, report): + """Get recomputation results from mongo collection for specific date and report + + Args: + collection (obj.): pymongo collection object + target_date (str.): date to target + report (str.): report name + + Returns: + list(dict): list of dictionaries containing recomputation definitions + """ + + # Init results list + results = [] + # prepare the query to find requests that include the target date + query = "'%s' >= this.start_time.split('T')[0] && '%s' <= this.end_time.split('T')[0]" % (target_date, target_date) + # run the query + for item in collection.find({"report":report,"$where": query}, {"_id": 0}): + results.append(item) + + return results + +def upload_recomputations(tenant, report, target_date, config): + """Given a tenant, report and target date upload the relevant recomputations + as an hdfs file + + Args: + tenant (str.): tenant name + report (str.): report name + target_date (str.): target date + config (obj.): argo configuration object + + Returns: + bool: True if upload was succesfull + """ + + tenant_group = "TENANTS:" +tenant + mongo_endpoint = config.get("MONGO","endpoint").geturl() + mongo_location = config.get_default(tenant_group,"mongo_uri").fill(mongo_endpoint=mongo_endpoint,tenant=tenant) + col = get_mongo_collection(mongo_location, "recomputations" ) + recomp_data = get_mongo_results(col, target_date, report) + return write_output(recomp_data, tenant, report, target_date, config) + + + +def main(args=None): + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + res = upload_recomputations(args.tenant, args.report, args.date, config) + if not res: + sys.exit(1) + +if __name__ == "__main__": + + # Feed Argument parser with the description of the 3 arguments we need + # (input_file,output_file,schema_file) + arg_parser = ArgumentParser(description="Get relevant recomputation requests") + arg_parser.add_argument( + "-d", "--date", help="date", dest="date", metavar="DATE", required="TRUE") + arg_parser.add_argument( + "-t", "--tenant", help="tenant owner ", dest="tenant", metavar="STRING", required="TRUE") + arg_parser.add_argument( + "-r", "--report", help="report ", dest="report", metavar="STRING", required="TRUE") + arg_parser.add_argument( + "-c", "--config", help="config ", dest="config", metavar="STRING", required="TRUE") + # Parse the command line arguments accordingly and introduce them to + # main... + sys.exit(main(arg_parser.parse_args())) \ No newline at end of file diff --git a/bin/utils/test_argo_config.py b/bin/utils/test_argo_config.py new file mode 100644 index 00000000..8bf80ca2 --- /dev/null +++ b/bin/utils/test_argo_config.py @@ -0,0 +1,34 @@ +import unittest +import os +from argo_config import ArgoConfig +from urlparse import urlparse + + +CONF_FILE = os.path.join(os.path.dirname(__file__), '../../conf/argo-streaming.conf') +SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../../conf/config.schema.json') + + +class TestClass(unittest.TestCase): + + def test_config(self): + argo_conf = ArgoConfig() + argo_conf.load_conf(CONF_FILE) + argo_conf.load_schema(SCHEMA_FILE) + argo_conf.check_conf() + + # get fixed HDFS -> user param (string) + self.assertEqual("foo", argo_conf.get("HDFS", "user")) + # get var TENANTS:TENANT_A -> reports param (list of strings) + self.assertEqual(["report1", "report2"], argo_conf.get("TENANTS:TENANT_A", "reports")) + # get var TENANTS:TENANT_B -> mongo_uri (url) + self.assertEqual(urlparse("mongodb://localhost:21017/argo_FOO"), + argo_conf.get("TENANTS:TENANT_B", "mongo_uri").fill(mongo_uri=argo_conf.get("MONGO","endpoint").geturl(),tenant="FOO")) + + # get var HDFS -> path_metric (template) and fill it with specific arguments + exp_result = urlparse("hdfs://localhost:2000/user/foobar/argo/tenants/wolf/mdata") + self.assertEqual(exp_result, argo_conf.get("HDFS", "path_metric").fill(namenode="localhost:2000", hdfs_user="foobar", + tenant="wolf")) + # fill template with different user argument + self.assertNotEqual(exp_result, + argo_conf.get("HDFS", "path_metric").fill(namenode="localhost:2000", hdfs_user="foobar2", + tenant="wolf")) diff --git a/bin/utils/test_update_ams.py b/bin/utils/test_update_ams.py new file mode 100644 index 00000000..20fe1b85 --- /dev/null +++ b/bin/utils/test_update_ams.py @@ -0,0 +1,174 @@ +import unittest +import responses +from update_ams import ArgoAmsClient + + +class TestClass(unittest.TestCase): + + def test_urls(self): + ams = ArgoAmsClient("foo.host", "secret_key") + + test_cases = [ + {"resource": "projects", "item_uuid": None, "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/projects?key=secret_key"}, + {"resource": "projects", "item_uuid": "TEST_PROJECT", "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/projects/TEST_PROJECT?key=secret_key"}, + {"resource": "projects", "item_uuid": "TEST_PROJECT2", "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/projects/TEST_PROJECT2?key=secret_key"}, + {"resource": "users", "item_uuid": None, "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/users?key=secret_key"}, + {"resource": "users", "item_uuid": "userA", "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/users/userA?key=secret_key"}, + {"resource": "users", "item_uuid": "userB", "group_uuid": None, "action": None, + "expected": "https://foo.host/v1/users/userB?key=secret_key"}, + {"resource": "topics", "item_uuid": None, "group_uuid": "PROJECT_A", "action": None, + "expected": "https://foo.host/v1/projects/PROJECT_A/topics?key=secret_key"}, + {"resource": "topics", "item_uuid": "topic101", "group_uuid": "PROJECT_A", "action": None, + "expected": "https://foo.host/v1/projects/PROJECT_A/topics/topic101?key=secret_key"}, + {"resource": "topics", "item_uuid": "topic102", "group_uuid": "PROJECT_A", "action": "acl", + "expected": "https://foo.host/v1/projects/PROJECT_A/topics/topic102:acl?key=secret_key"}, + {"resource": "subscriptions", "item_uuid": None, "group_uuid": "PROJECT_A", "action": None, + "expected": "https://foo.host/v1/projects/PROJECT_A/subscriptions?key=secret_key"}, + {"resource": "subscriptions", "item_uuid": "sub101", "group_uuid": "PROJECT_A", "action": None, + "expected": "https://foo.host/v1/projects/PROJECT_A/subscriptions/sub101?key=secret_key"}, + {"resource": "subscriptions", "item_uuid": "sub102", "group_uuid": "PROJECT_A", "action": "acl", + "expected": "https://foo.host/v1/projects/PROJECT_A/subscriptions/sub102:acl?key=secret_key"}, + + ] + + for test_case in test_cases: + actual = ams.get_url(test_case["resource"], test_case["item_uuid"], test_case["group_uuid"], + test_case["action"]) + expected = test_case["expected"] + self.assertEquals(expected, actual) + + @responses.activate + def test_basic_request(self): + # prepare fake responses for ams + + responses.add(responses.GET, 'https://ams.foo/v1/projects/PROJECTA?key=faketoken', + json={ + "name": "PROJECTA", + "created_on": "2018-03-27T15:56:28Z", + "modified_on": "2018-03-27T15:56:28Z", + "created_by": "foo_user_admin" + }, status=200) + responses.add(responses.GET, 'https://ams.foo/v1/users?key=faketoken', + json={"users": [{ + "uuid": "id01", + "projects": [ + { + "project": "PROJECTA", + "roles": [ + "publisher" + ], + "topics": [ + "sync_data", + "metric_data" + ], + "subscriptions": [] + } + ], + "name": "ams_projectA_publisher", + + }, { + "uuid": "id02", + "projects": [ + { + "project": "PROJECTA", + "roles": [ + "consumer" + ], + "topics": [ + + ], + "subscriptions": ["ingest_sync", + "ingest_metric", + "status_sync", + "status_metric"] + } + ], + "name": "ams_projecta_consumer", + }] + }, status=200) + responses.add(responses.GET, 'https://ams.foo/v1/users/ams_projecta_consumer?key=faketoken', + json={ + "uuid": "id02", + "projects": [ + { + "project": "PROJECTA", + "roles": [ + "publisher" + ], + "topics": [ + + ], + "subscriptions": ["ingest_metric", + "status_metric"] + } + ], + "name": "ams_projecta_consumer", + + }, status=200) + responses.add(responses.GET, 'https://ams.foo/v1/users/ams_projecta_publisher?key=faketoken', + json={ + "uuid": "id02", + "projects": [ + { + "project": "PROJECTA", + "roles": [ + "consumer" + ], + "topics": ["sync_data", + "metric_data" + + ], + "subscriptions": [] + } + ], + "name": "ams_projecta_consumer", + + }, status=200) + + responses.add(responses.GET, 'https://ams.foo/v1/projects/PROJECTA/topics?key=faketoken', + json={"topics": [{ + "name": "projects/PROJECTA/topics/metric_data" + }] + }, status=200) + + responses.add(responses.GET, 'https://ams.foo/v1/projects/PROJECTA/subscriptions?key=faketoken', + json={"subscriptions": [{ + "name": "projects/PROJECTA/subscriptions/ingest_metric", + "topic": "projects/PROJECTA/topic/metric_data" + }, + { + "name": "projects/PROJECTA/subscriptions/status_metric", + "topic": "projects/PROJECTA/topic/metric_data" + } + ] + }, status=200) + responses.add(responses.GET, 'https://ams.foo/v1/users/ams_projecta_admin?key=faketoken', + json={"error": {"message": "user not found"} + }, status=404) + + ams = ArgoAmsClient("ams.foo", "faketoken") + + self.assertEquals("PROJECTA", ams.get_project("PROJECTA")["name"]) + users = ams.get_users() + self.assertEquals("id01", users[0]["uuid"]) + self.assertEquals("id02", users[1]["uuid"]) + user = ams.get_user("ams_projecta_consumer") + self.assertEquals("ams_projecta_consumer", user["name"]) + + self.assertEquals(["sync_data", "metric_data"], ams.user_get_topics(users[0], "PROJECTA")) + self.assertEquals([], ams.user_get_subs(users[0], "PROJECTA")) + self.assertEquals([], ams.user_get_topics(users[1], "PROJECTA")) + self.assertEquals(["ingest_sync", "ingest_metric", "status_sync", "status_metric"], + ams.user_get_subs(users[1], "PROJECTA")) + + self.assertEquals("PROJECTA", ams.check_project_exists("projectA")["name"]) + expected_missing = {'topics': ['sync_data'], 'topic_acls': [], + 'subs': ['ingest_sync', 'ingest_metric', 'status_sync', 'status_metric'], + 'sub_acls': ['ingest_sync'], 'users': ['project_admin']} + + self.assertEquals(expected_missing, ams.check_tenant("projectA")) diff --git a/bin/utils/test_update_cron.py b/bin/utils/test_update_cron.py new file mode 100644 index 00000000..1fe8b234 --- /dev/null +++ b/bin/utils/test_update_cron.py @@ -0,0 +1,134 @@ +import unittest +import os +from update_cron import get_daily, get_hourly, gen_entry, gen_batch_ar, gen_batch_status, gen_tenant_all, gen_for_all +from argo_config import ArgoConfig + +CONF_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../conf/argo-streaming.conf')) +SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../../conf/config.schema.json') + +# relative path to ar job submit script +BATCH_AR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../ar_job_submit.py')) +# relative path to status job submit script +BATCH_STATUS = os.path.abspath(os.path.join(os.path.dirname(__file__), '../status_job_submit.py')) +# bash argument to get today's date (utc) +TODAY = """$(/bin/date --utc +\%Y-\%m-\%d)""" +# bash argument to get previous date date (utc) +YESTERDAY = """$(/bin/date --utc --date '-1 day' +\%Y-\%m-\%d)""" + + +class TestClass(unittest.TestCase): + + def test_update_cron(self): + + config = ArgoConfig(CONF_FILE, SCHEMA_FILE) + + # Test get_hourly + self.assertEquals("8 * * * *", get_hourly(8)) + self.assertEquals("44 * * * *", get_hourly(44)) + self.assertEquals("32 * * * *", get_hourly(32)) + self.assertEquals("12 * * * *", get_hourly(12)) + self.assertEquals("5 * * * *", get_hourly()) + + # Test get_daily + self.assertEquals("8 1 * * *", get_daily(1, 8)) + self.assertEquals("44 3 * * *", get_daily(3, 44)) + self.assertEquals("32 4 * * *", get_daily(4, 32)) + self.assertEquals("12 5 * * *", get_daily(5, 12)) + self.assertEquals("0 5 * * *", get_daily()) + + # Test gen_entry + self.assertEquals("#simple command\n5 * * * * root echo 12\n", + gen_entry(get_hourly(), "echo 12", "root", "simple command")) + + self.assertEquals("#foo command\n8 12 * * * foo echo 1+1\n", + gen_entry(get_daily(12, 8), "echo 1+1", "foo", "foo command")) + + # Test generation of ar cronjob for a specific tenant and report + expected = "#TENANT_A:report1 daily A/R\n"\ + + "5 * * * * foo " + BATCH_AR + " -t TENANT_A -r report1 -d " + YESTERDAY + " -m upsert " + "-c "\ + + config.conf_path + "\n" + + self.assertEquals(expected, gen_batch_ar(config, "TENANT_A", "report1", "daily", "foo", "upsert")) + + # Test generation of ar cronjob for a specific tenant and report + expected = "#TENANT_A:report1 hourly A/R\n"\ + + "5 5 * * * " + BATCH_AR + " -t TENANT_A -r report1 -d " + TODAY + " -m insert " + "-c "\ + + config.conf_path + "\n" + + self.assertEquals(expected, gen_batch_ar(config, "TENANT_A", "report1", "hourly")) + + # Test generation of ar cronjob for a specific tenant and report + expected = "#TENANT_B:report1 daily Status\n"\ + + "5 * * * * foo " + BATCH_STATUS + " -t TENANT_B -r report1 -d " \ + + YESTERDAY + " -m upsert " + "-c "\ + + config.conf_path + "\n" + + self.assertEquals(expected, gen_batch_status(config, "TENANT_B", "report1", "daily", "foo", "upsert")) + + # Test generation of status cronjob for a specific tenant and report + expected = "#TENANT_B:report1 hourly Status\n"\ + + "5 5 * * * " + BATCH_STATUS + " -t TENANT_B -r report1 -d " + TODAY + " -m insert " + "-c "\ + + config.conf_path + "\n" + + self.assertEquals(expected, gen_batch_status(config, "TENANT_B", "report1", "hourly")) + + # Test generation of cronjobs for a tenant's reports + expected = "#Jobs for TENANT_A\n\n" \ + + "#TENANT_A:report1 hourly A/R\n" \ + + "5 5 * * * " + BATCH_AR + " -t TENANT_A -r report1 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report1 daily A/R\n" \ + + "5 * * * * " + BATCH_AR + " -t TENANT_A -r report1 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report1 hourly Status\n" \ + + "5 5 * * * " + BATCH_STATUS + " -t TENANT_A -r report1 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report1 daily Status\n" \ + + "5 * * * * " + BATCH_STATUS + " -t TENANT_A -r report1 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report2 hourly A/R\n" \ + + "5 5 * * * " + BATCH_AR + " -t TENANT_A -r report2 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report2 daily A/R\n" \ + + "5 * * * * " + BATCH_AR + " -t TENANT_A -r report2 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report2 hourly Status\n" \ + + "5 5 * * * " + BATCH_STATUS + " -t TENANT_A -r report2 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_A:report2 daily Status\n" \ + + "5 * * * * " + BATCH_STATUS + " -t TENANT_A -r report2 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "\n" + + self.assertEquals(expected, gen_tenant_all(config, "TENANT_A")) + + # Test generation of cronjobs for all tenants and all reports + expected2 = "#Jobs for TENANT_B\n\n" \ + + "#TENANT_B:report1 hourly A/R\n" \ + + "5 5 * * * " + BATCH_AR + " -t TENANT_B -r report1 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report1 daily A/R\n" \ + + "5 * * * * " + BATCH_AR + " -t TENANT_B -r report1 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report1 hourly Status\n" \ + + "5 5 * * * " + BATCH_STATUS + " -t TENANT_B -r report1 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report1 daily Status\n" \ + + "5 * * * * " + BATCH_STATUS + " -t TENANT_B -r report1 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report2 hourly A/R\n" \ + + "5 5 * * * " + BATCH_AR + " -t TENANT_B -r report2 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report2 daily A/R\n" \ + + "5 * * * * " + BATCH_AR + " -t TENANT_B -r report2 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report2 hourly Status\n" \ + + "5 5 * * * " + BATCH_STATUS + " -t TENANT_B -r report2 -d " + TODAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "#TENANT_B:report2 daily Status\n" \ + + "5 * * * * " + BATCH_STATUS + " -t TENANT_B -r report2 -d " + YESTERDAY + " -m insert -c " \ + + config.conf_path + "\n\n" \ + + "\n" + + expected = expected + expected2 + self.assertEquals(expected, gen_for_all(config)) diff --git a/bin/utils/test_update_profiles.py b/bin/utils/test_update_profiles.py index 0524f09f..c162d722 100644 --- a/bin/utils/test_update_profiles.py +++ b/bin/utils/test_update_profiles.py @@ -1,10 +1,6 @@ import unittest -import os from update_profiles import HdfsReader from update_profiles import ArgoApiClient -from update_profiles import get_config - -CONF_TEMPLATE = os.path.join(os.path.dirname(__file__), '../../conf/conf.template') class TestClass(unittest.TestCase): @@ -60,29 +56,14 @@ def test_api(self): {"resource": "aggregations", "item_uuid": None, "expected": "https://foo.host/api/v2/aggregation_profiles"}, {"resource": "aggregations", "item_uuid": "12", - "expected": "https://foo.host/api/v2/aggregation_profiles/12"} + "expected": "https://foo.host/api/v2/aggregation_profiles/12"}, + {"resource": "tenants", "item_uuid": None, + "expected": "https://foo.host/api/v2/admin/tenants"}, + {"resource": "tenants", "item_uuid": "12", + "expected": "https://foo.host/api/v2/admin/tenants/12"} ] for test_case in test_cases: actual = argo_api.get_url(test_case["resource"], test_case["item_uuid"]) expected = test_case["expected"] self.assertEquals(expected, actual) - - def test_cfg(self): - - actual_cfg = get_config(CONF_TEMPLATE) - - expected_cfg = { - 'hdfs_host': 'hdfs_test_host', - 'hdfs_port': 'hdfs_test_port', - 'hdfs_user': 'hdfs_test_user', - 'hdfs_writer': '/path/to/binary', - 'hdfs_sync': 'hdfs://{{hdfs_host}}:{{hdfs_port}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/sync', - 'log_level': 'info', - 'api_host': 'api.foo', - 'tenant_keys': {'TA': 'key1', - 'TB': 'key2', - 'TC': 'key3'} - } - - self.assertEquals(expected_cfg, actual_cfg) diff --git a/bin/utils/update_ams.py b/bin/utils/update_ams.py new file mode 100755 index 00000000..d9148830 --- /dev/null +++ b/bin/utils/update_ams.py @@ -0,0 +1,804 @@ +#!/usr/bin/env python +import requests +import json +import logging +from common import get_config_paths, get_log_conf +from argo_config import ArgoConfig +from argparse import ArgumentParser +import sys + + +log = logging.getLogger(__name__) + + +class ArgoAmsClient: + """ + ArgoAmsClient class + + It connects to an argo-messaging host and retrieves project/user/topic/subscription information + """ + + def __init__(self, host, admin_key, verify=True): + """ + Initialize ArgoAAmsClient + Args: + host: str. argo ams host + admin_key: str. admin token + """ + + # flag to verify https connections or not + self.verify = verify + # ams host + self.host = host + # admin key to access ams service + self.admin_key = admin_key + + # ams api resource paths + self.paths = dict() + self.paths.update({ + 'projects': '/v1/projects', + 'users': '/v1/users', + 'topics': '/v1/projects/{}/topics', + 'subscriptions': '/v1/projects/{}/subscriptions', + }) + + def get_url(self, resource, item_id=None, group_id=None, action=None): + """ + Constructs an ams url based on resource, item and group(project) + Args: + resource: str. resource to be retrieved (projects/users/topics/susbscriptions) + item_id: str. retrieve a specific item from the resource + group_id: str. retrieve item from a specific group (grouped resource) + action: str. additional actions for some urls (such as acl, modifyAcl etc) + + Returns: + str: url path + + """ + # get resource path + resource_path = self.paths[resource] + # if grouped resource (like topics or subs that belong under a project) + if '{}' in resource_path: + # add groups name into the resource path + resource_path = resource_path.format(group_id) + # if a resource item is not specified get all items + if item_id is None: + return "".join(["https://", self.host, resource_path, "?key=", self.admin_key]) + else: + # if a specific item is given check if an additional api action is specified on item (like :acl on topics) + if action is not None: + return "".join( + ["https://", self.host, resource_path, "/", item_id, ":", action, "?key=", self.admin_key]) + return "".join(["https://", self.host, resource_path, "/", item_id, "?key=", self.admin_key]) + + def post_resource(self, url, data): + """ + Posts (inserts) an ams resource by token + Args: + url: str. url of resource + data: dict. data to post + + Returns: + dict: resource contents + + """ + # Set up headers + headers = dict() + headers.update({ + 'Accept': 'application/json' + }) + # do the post requests + r = requests.post(url, headers=headers, verify=self.verify, data=json.dumps(data)) + # if successful return data (or empty json) + if 200 == r.status_code: + if r.text == "": + return {} + return json.loads(r.text) + # If not successful return None + else: + log.critical(r.text) + return None + + def put_resource(self, url, data): + """ + Puts (inserts/updates) an ams resource by token + Args: + url: str. url of resource + data: dict. data to post + + Returns: + dict: resource contents + + """ + # Set up headers + headers = dict() + headers.update({ + 'Accept': 'application/json' + }) + # do the put request + r = requests.put(url, headers=headers, verify=self.verify, data=json.dumps(data)) + # if successful return json data (or empty json) + if 200 == r.status_code: + if r.text == "": + return {} + return json.loads(r.text) + # if not successful return None + else: + log.critical(r.text) + return None + + def get_resource(self, url): + """ + Returns an ams resource by token + Args: + url: str. url of resource + + Returns: + dict: resource contents + + """ + # Set up headers + headers = dict() + headers.update({ + 'Accept': 'application/json' + }) + # do the get resource + r = requests.get(url, headers=headers, verify=self.verify) + # if successful return the json data or empty json + if 200 == r.status_code: + if r.text == "": + return {} + return json.loads(r.text) + else: + return None + + def get_project(self, project): + """ + Get a specific project from AMS + Args: + project: str. name of the project + + Returns: + dict. json representation of the project + + """ + url = self.get_url("projects", project) + return self.get_resource(url) + + def get_users(self): + """ + Get the list of users from AMS + + Returns: + dict. json representation of the list of users + + """ + url = self.get_url("users") + return self.get_resource(url)["users"] + + def get_user(self, name): + """ + Get a specific AMS user + Args: + name: str. username + + Returns: + dict. json representation of the AMS user + + """ + url = self.get_url("users", name) + return self.get_resource(url) + + def get_topics(self, project): + """ + Get list of topics for an AMS project + Args: + project: str. name of the project + + Returns: + dict. json representation of the list of topics + + """ + url = self.get_url("topics", None, project) + return self.get_resource(url) + + def get_subs(self, project): + """ + Get list of subs for an AMS project + Args: + project: str. name of the project + + Returns: + dict. json represenatation of the list of subscriptions + + """ + url = self.get_url("subscriptions", None, project) + return self.get_resource(url) + + def get_topic_num_of_messages(self, project, topic): + """ + Get number of messages arrived in a topic + Args: + project: str. project name + topic: str. topic name + + Returns: + int. number of messages + + """ + url = self.get_url("topics", topic, project, "metrics") + metrics = self.get_resource(url)["metrics"] + + for metric in metrics: + if metric["metric"] == "topic.number_of_messages": + ts = metric["timeseries"] + if len(ts) > 0: + return ts[0]["value"] + return 0 + + + def get_topic_acl(self, project, topic): + """ + Get ACL list for a specific project's topic + Args: + project: str. project name + topic: str. topic name + + Returns: + list. a list of authorized users + + """ + url = self.get_url("topics", topic, project, "acl") + return self.get_resource(url)["authorized_users"] + + def get_sub_acl(self, project, sub): + """ + Get ACL list for a specific project's subscription + Args: + project: str. project name + sub: str. topic name + + Returns: + list. a list of authorized users + + """ + url = self.get_url("subscriptions", sub, project, "acl") + return self.get_resource(url)["authorized_users"] + + def get_tenant_project(self, tenant): + """ + For a given tenant name get the corresponding project in AMS + Args: + tenant: str. tenant name + + Returns: + dict. json representation of AMS project + + """ + project = tenant.upper() + return self.get_project(project) + + def get_tenant_user(self, tenant, role): + """ + For a given tenant and role get the corresponding AMS user + Args: + tenant: str. tenant name + role: str. user role (project_admin|consumer|publisher) + + Returns: + dict. json representation of AMS user + + """ + if role == "project_admin": + username = "ams_{}_admin".format(tenant.lower()) + else: + username = "ams_{}_{}".format(tenant.lower(), role) + return self.get_user(username) + + def get_tenant_users(self, tenant): + """ + For a given tenant get a list of all AMS users associated with this tenant + + Args: + tenant: str. tenant name + + Returns: + dict. json representation of list of AMS users + + """ + # tenant must have 3 users: project_admin, publisher, consumer + lookup = [("project_admin", "ams_{}_admin"), ("publisher", "ams_{}_publisher"), ("consumer", "ams_{}_consumer")] + lookup = [(x, y.format(tenant.lower())) for (x, y) in lookup] + users = dict() + for (role, name) in lookup: + data = self.get_user(name) + if data is not None: + users[role] = data + + return users + + def get_tenant_topics(self, tenant): + """ + For a specific tenant get all relevant AMS topics + Args: + tenant: str. tenant name + + Returns: + dict. A set of relevant AMS topics + + """ + project = tenant.upper() + found = dict() + topics = self.get_topics(project) + + if topics is not None and "topics" in topics: + topics = topics['topics'] + for topic in topics: + name = topic["name"] + if name.endswith('sync_data'): + found["sync_data"] = name + if name.endswith('metric_data'): + found["metric_data"] = name + return found + + def get_tenant_subs(self, tenant, topics): + """ + For a given tenant and specific topics get relevant subscriptions + tied to those topics + Args: + tenant: str. tenant name + topics: list. list of ams topics + + Returns: + dict. a set of relevant subscriptions + + """ + project = tenant.upper() + found = dict() + subs = self.get_subs(project) + if subs is not None and "subscriptions" in subs: + subs = subs['subscriptions'] + for sub in subs: + name = sub["name"] + if name.endswith('ingest_sync'): + if topics["sync_data"] == sub["topic"]: + found["ingest_sync"] = name + if name.endswith('ingest_metric'): + if topics["metric_data"] == sub["topic"]: + found["ingest_metric"] = name + if name.endswith('status_sync'): + if topics["sync_data"] == sub["topic"]: + found["status_sync"] = name + if name.endswith('status_metric'): + if topics["metric_data"] == sub["topic"]: + found["status_metric"] = name + return found + + @staticmethod + def user_get_topics(user, project): + """ + Given an AMS user definition and a specific project + get project's topics that this user has access to + Args: + user: dict. AMS user representation + project: str. project name + + Returns: + list(str) list of topics + + """ + for item in user['projects']: + if item['project'] == project: + return item['topics'] + return [] + + @staticmethod + def user_get_subs(user, project): + """ + Given an AMS user definition and a specific project + get project's subscriptions that this user has access to + Args: + user: dict. AMS user representation + project: str. project name + + Returns: + list(str) list of subscriptions + + """ + for item in user['projects']: + if item['project'] == project: + return item['subscriptions'] + return [] + + def update_tenant_configuration(self, tenant, config): + """ + Given a tenant and an instance of argo configuration + update argo configuration with latest tenant's AMS details + Args: + tenant: str. tenant name + config: obj. ArgoConfig object + """ + + # Get tenant's section in argo configuration + section_tenant = "TENANTS:" + tenant + + # update tenant ams project + ams_project = self.get_tenant_project(tenant) + + if ams_project is not None: + if ams_project["name"] != config.get(section_tenant, "ams_project"): + config.set(section_tenant, "ams_project", ams_project["name"]) + + # update tenant ams_token + ams_consumer = self.get_tenant_user(tenant, "consumer") + + if ams_consumer["token"] != config.get(section_tenant, "ams_token"): + config.set(section_tenant, "ams_token", ams_consumer["token"]) + + # get tenant's job sections in argo configuration + section_ingest_metric = "TENANTS:{}:ingest-metric".format(tenant) + section_ingest_sync = "TENANTS:{}:ingest-sync".format(tenant) + section_status_stream = "TENANTS:{}:stream-status".format(tenant) + + # update tenant ingest-metric + if config.get(section_ingest_metric, "ams_sub") != "ingest_metric": + config.set(section_ingest_metric, "ams_sub", "ingest_metric") + + # update tenant ingest-sync + if config.get(section_ingest_sync, "ams_sub") != "ingest_sync": + config.set(section_ingest_sync, "ams_sub", "ingest_sync") + + # update tenant status_stream + if config.get(section_status_stream, "ams_sub_metric") != "status_metric": + config.set(section_status_stream, "ams_sub_metric", "status_metric") + + if config.get(section_status_stream, "ams_sub_sync") != "status_sync": + config.set(section_status_stream, "ams_sub_sync", "status_sync") + + def create_tenant_project(self, tenant): + """ + For a given tenant create an AMS project + Args: + tenant: str. tenant name + + Returns: + dict. json representation of the newly created AMS project + + """ + project_name = tenant.upper() + url = self.get_url("projects", project_name) + data = {"description": "generated by argo engine"} + return self.post_resource(url, data) + + def create_tenant_topic(self, tenant, topic): + """ + For a given tenant and topic name create a new AMS topic + Args: + tenant: str. tenant name + topic: str. topic name + + Returns: + dict. json representation of the newly created AMS topic + + """ + project_name = tenant.upper() + url = self.get_url("topics", topic, project_name) + return self.put_resource(url, "") + + def create_tenant_sub(self, tenant, topic, sub): + """ + For a given tenant, topic and subscription name create a new AMS subscription + tied to that topic + + Args: + tenant: str. tenant name + topic: str. topic name + sub: str. subscription name + + Returns: + dict. json representation of the newly created AMS subscription + + """ + project_name = tenant.upper() + url = self.get_url("subscriptions", sub, project_name) + data = {"topic": "projects/{}/topics/{}".format(project_name, topic)} + return self.put_resource(url, data) + + def create_tenant_user(self, tenant, role): + """ + For a given tenant and user role create a new user tied to that tenant + Args: + tenant: str. tenant name + role: str. user role (project_admin|consumer|publisher) + + Returns: + dict: json representation of the newly created AMS user + + """ + project_name = tenant.upper() + if role == "project_admin": + username = "ams_{}_admin".format(tenant.lower()) + else: + username = "ams_{}_{}".format(tenant.lower(), role) + + print username, role + url = self.get_url("users", username) + data = {"projects": [{"project": project_name, "roles": [role]}]} + return self.post_resource(url, data) + + def set_topic_acl(self, tenant, topic, acl): + """ + For a given tenant, topic and acl list, update topic's acl + + Args: + tenant: str. tenant name + topic: str. topic name + acl: list(str). list of authorized usernames + + Returns: + dict. json representation of the updated AMS acl + """ + project_name = tenant.upper() + url = self.get_url("topics", topic, project_name, "modifyAcl") + data = {"authorized_users": acl} + return self.post_resource(url, data) + + def set_sub_acl(self, tenant, sub, acl): + """ + For a given tenant, subscription and acl list, update subscription's acl + + Args: + tenant: str. tenant name + sub: str. subscription name + acl: list(str). list of authorized usernames + + Returns: + dict. json representation of the updated AMS acl + """ + project_name = tenant.upper() + url = self.get_url("subscriptions", sub, project_name, "modifyAcl") + data = {"authorized_users": acl} + return self.post_resource(url, data) + + def check_tenant(self, tenant): + """ + For a given tenant check AMS for missing tenant definitions (topics,subs,users,acls) + Args: + tenant: str. tenant name + + Returns: + dict: a dictionary containing missing topics,subs,users and acls + + """ + + project = tenant.upper() + + # Things that sould be present in AMS definitions + topics_lookup = ["sync_data", "metric_data"] + subs_lookup = ["ingest_sync", "ingest_metric", "status_sync", "status_metric"] + users_lookup = ["project_admin", "publisher", "consumer"] + topic_acl_lookup = ["sync_data", "metric_data"] + sub_acl_lookup = ["ingest_sync", "ingest_metric"] + + # Initialize a dictionary with missing definitions + missing = dict() + missing["topics"] = list() + missing["subs"] = list() + missing["users"] = list() + missing["topic_acls"] = list() + missing["sub_acls"] = list() + + # Get tenant's AMS topics, subs and users + topics = self.get_tenant_topics(tenant) + subs = self.get_tenant_subs(tenant, topics) + users = self.get_tenant_users(tenant) + + # If AMS definitions are None create empty lists + if topics is None: + topics = {} + if subs is None: + subs = {} + if users is None: + users = {} + + # For each expected topic check if it was indeed found in AMS or if it's missing + for item in topics_lookup: + if item not in topics.keys(): + missing["topics"].append(item) + + # For each expected sub check if it was indeed found in AMS or if it's missing + for item in subs_lookup: + if item not in subs.keys(): + missing["subs"].append(item) + + # For each expected user check if it was indeed found in AMS or if it's missing + for item in users_lookup: + if item not in users.keys(): + missing["users"].append(item) + + user_topics = [] + user_subs = [] + + # Get publisher topics + if "publisher" in users: + user_topics = self.user_get_topics(users["publisher"], project) + + # Check expected topic acls if are missing + for item in topic_acl_lookup: + if item not in user_topics: + missing["topic_acls"].append(item) + # Get consumers subscriptions + if "consumer" in users: + user_subs = self.user_get_subs(users["consumer"], project) + + # check expected sub acls if are missing + for item in sub_acl_lookup: + if item not in user_subs: + missing["sub_acls"].append(item) + + return missing + + def fill_missing(self, tenant, missing): + """ + Giving a dictionary with missing tenant definitions (returned from check_tenant method) attempt to create + the missing defnitions in AMS + + Args: + tenant: str. tenant name + missing: dict. tenant's missing definitions (topics,subs,users,acls) + + """ + + # For each missing topic attempt to create it in AMS + for topic in missing["topics"]: + # create topic + topic_new = self.create_tenant_topic(tenant, topic) + log.info("Tenant:{} - created missing topic: {}".format(tenant, topic_new["name"])) + + # For each missing sub attempt to create it in AMS + for sub in missing["subs"]: + # create sub + if sub.endswith("metric"): + topic = "metric_data" + elif sub.endswith("sync"): + topic = "sync_data" + else: + continue + sub_new = self.create_tenant_sub(tenant, topic, sub) + log.info("Tenant:{} - created missing subscription: {} on topic: {}".format(tenant, sub_new["name"], + sub_new["topic"])) + + # For each missing user attempt to create it in AMS + for user in missing["users"]: + # create user + user_new = self.create_tenant_user(tenant, user) + log.info("Tenant:{} - created missing user: {}".format(tenant, user_new["name"])) + + # For each missing topic acl attempt to set it in AMS + for topic_acl in missing["topic_acls"]: + acl = self.get_topic_acl(tenant, topic_acl) + user_pub = "ams_{}_publisher".format(tenant.lower()) + if user_pub not in acl: + acl.append(user_pub) + + r = self.set_topic_acl(tenant, topic_acl, acl) + if r is not None: + log.info("Tenant:{} - set missing acl on topic: {} for user: {}".format(tenant, topic_acl, user_pub)) + + # For each missing subscription attempt to set it in AMS + for sub_acl in missing["sub_acls"]: + acl = self.get_sub_acl(tenant, sub_acl) + user_con = "ams_{}_consumer".format(tenant.lower()) + if user_con not in acl: + acl.append(user_con) + + r = self.set_sub_acl(tenant, sub_acl, acl) + if r is not None: + log.info( + "Tenant:{} - set missing acl on subscription: {} for user: {}".format(tenant, sub_acl, user_con)) + + def check_project_exists(self, tenant): + """ + Given a tenant name check if corresponding AMS project exists + If doesnt exist calls create_tenant_project() method to create it + Args: + tenant: str. tenant name + + Returns: + dict. json representation of the project + """ + # check if project exists + project = self.get_tenant_project(tenant) + if project is None: + # if project doesn't exist attempt to create it + log.info("{} project not found on ams".format(tenant)) + project = self.create_tenant_project(tenant) + log.info("{} project created for tenant: {}".format(project["name"], tenant)) + return project + log.info("{} project found for tenant: {}".format(project["name"], tenant)) + return project + + +def is_tenant_complete(missing): + """ + Given a dict of missing tenant definitions, check if the missing + definitions are empty thus the tenant is complete + Args: + missing: dict. tenant's missing definitions (topics,subs,users,acls) + + Returns: + bool: True if tenant's missing definitions are empty + + """ + check_list = ["topics", "subs", "users", "topic_acls", "sub_acls"] + for item in check_list: + if len(missing[item]) > 0: + return False + return True + + +def run_ams_update(args): + """ + Runs when this script is called from CLI. + Runs for a specific tenant or all tenants in argo config + Reads argo configuration tenant list. For each tenant, creates missing tenant's definitions + and updates argo configuration with tenant's ams parameters + + Args: + args: cli arguments + + """ + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + + ams_token = config.get("AMS", "access_token") + ams_host = config.get("AMS", "endpoint").hostname + log.info("ams api used {}".format(ams_host)) + + tenant_list = config.get("API", "tenants") + ams = ArgoAmsClient(ams_host, ams_token) + + if args.tenant is not None: + # Check if tenant exists in argo configuarion + if args.tenant not in tenant_list: + log.fatal("Tenant:{} is not in argo configuration - exiting...".format(args.tenant)) + sys.exit(1) + # Check only the tenant that user specified + check_tenants = [args.tenant] + else: + # Check all tenants that are specified in argo configuration + check_tenants = tenant_list + + for tenant in check_tenants: + ams.check_project_exists(tenant) + missing = ams.check_tenant(tenant) + if is_tenant_complete(missing): + log.info("Tenant {} definition on AMS is complete!".format(tenant)) + else: + ams.fill_missing(tenant, missing) + # Update tenant configuration + ams.update_tenant_configuration(tenant, config) + + # Save changes to designated configuration file + config.save_as(config.conf_path) + + +if __name__ == '__main__': + # Feed Argument parser with the description of the arguments we need + arg_parser = ArgumentParser( + description="update ams with relevant tenant configuration") + arg_parser.add_argument( + "-c", "--config", help="config", dest="config", metavar="STRING") + arg_parser.add_argument( + "-t", "--tenant", help="tenant", dest="tenant", metavar="STRING", required=False, default=None) + arg_parser.add_argument( + "-v", "--verify", help="verify", dest="verify", action="store_true") + + # Parse the command line arguments accordingly and introduce them to the run method + sys.exit(run_ams_update(arg_parser.parse_args())) diff --git a/bin/utils/update_cron.py b/bin/utils/update_cron.py new file mode 100755 index 00000000..0cb29f7b --- /dev/null +++ b/bin/utils/update_cron.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +import os +import sys +import tempfile +import logging +from argparse import ArgumentParser +from subprocess import check_output, CalledProcessError, check_call +from datetime import datetime +from common import get_log_conf, get_config_paths +from argo_config import ArgoConfig + + +log = logging.getLogger(__name__) + +# CONSTANTS +# relative path to ar job submit script +BATCH_AR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../ar_job_submit.py')) +# relative path to status job submit script +BATCH_STATUS = os.path.abspath(os.path.join(os.path.dirname(__file__), '../status_job_submit.py')) +# bash argument to get today's date (utc) +TODAY = """$(/bin/date --utc +\%Y-\%m-\%d)""" +# bash argument to get previous date date (utc) +YESTERDAY = """$(/bin/date --utc --date '-1 day' +\%Y-\%m-\%d)""" +# minute to start hourly cron jobs +HOURLY_MIN = 5 +# hour to start daily cron jobs +DAILY_HOUR = 5 + + +def get_hourly(minutes=5): + """ + Given a starting minute returns a cron string prefix for hourly repetition + + Args: + minutes: int. specify starting minute for hourly cron job + + Returns: str. hourly cron prefix + """ + return "{} * * * *".format(minutes) + + +def get_daily(hour=5, minutes=0): + """ + Given a starting hour and minute returns a cron string prefix for daily repetition + Args: + hour: int. specify starting hour for daily cron job + minutes: int. specify starting minute for daily cron job + + Returns: str. daily cron prefix + """ + return "{} {} * * *".format(minutes, hour) + + +def gen_entry(period, cmd, user=None, description=None): + """ + For a given period, cmd, user and description generate a text snippet + with the complete cron entry + + Args: + period: str. cron prefix specifying repetition + cmd: str. bash command to run + user: str. optional user that runs the command + description: str. text comment to accompany cron entry + + Returns: str. text snippet with cron entry + """ + txt = "" + if description is not None: + txt = "#{}\n".format(description) + + if user is not None: + txt = txt + "{} {} {}\n".format(period, user, cmd) + else: + txt = txt + "{} {}\n".format(period, cmd) + + return txt + + +def gen_batch_ar(config, tenant, report, iteration="hourly", user=None, mongo_method="insert"): + """ + For a given tenant and report generate a daily or hourly cron entry for ar batch job + Args: + config: obj. Argo Configuration object + tenant: str. tenant name + report: str. report name + iteration: str. either hourly or daily + user: str. optional user name + mongo_method: str. defines how to store data in mongodb, either insert or upsert + + Returns: str. text snippet with cron entry + """ + description = "{}:{} {} A/R".format(tenant, report, iteration) + + if iteration == "hourly": + # generate an hourly job + cron_prefix = get_daily(DAILY_HOUR, HOURLY_MIN) + # hourly jobs target today's date + target_date = TODAY + else: + # generate a daily job + cron_prefix = get_hourly(HOURLY_MIN) + # daily jobs target day before + target_date = YESTERDAY + + cmd = "{} -t {} -r {} -d {} -m {} -c {}".format(BATCH_AR, tenant, report, target_date, mongo_method, + os.path.abspath(config.conf_path)) + return gen_entry(cron_prefix, cmd, user, description) + + +def gen_batch_status(config, tenant, report, iteration="hourly", user=None, mongo_method="insert"): + """ + For a given tenant and report generate a daily or hourly cron entry for status batch job + Args: + config: obj. Argo Configuration + tenant: str. tenant name + report: str. report name + iteration: str. either hourly or daily + user: str. optional user name + mongo_method: str. defines how to store data in mongodb, either insert or upsert + + Returns: str. text snippet with cron entry + """ + description = "{}:{} {} Status".format(tenant, report, iteration) + + if iteration == "hourly": + # generate an hourly job + cron_prefix = get_daily(DAILY_HOUR, HOURLY_MIN) + # hourly jobs target today's date + target_date = TODAY + else: + # generate a daily job + cron_prefix = get_hourly(HOURLY_MIN) + # daily jobs target day before + target_date = YESTERDAY + cmd = "{} -t {} -r {} -d {} -m {} -c {}".format(BATCH_STATUS, tenant, report, target_date, mongo_method, + os.path.abspath(config.conf_path)) + return gen_entry(cron_prefix, cmd, user, description) + + +def gen_tenant_all(config, tenant, reports=None, user=None): + """ + For a given tenant and all of his reports generate a/r and status jobs + Args: + config: obj. Argo Configuration + tenant: str. tenant name + user: str. optional user name + + Returns: str. text snippet with all cron entries for this tenant + """ + cron_body = "#Jobs for {}\n\n".format(tenant) + section_tenant = "TENANTS:"+tenant + if reports is None: + reports = config.get(section_tenant, "reports") + mongo_method = config.get(section_tenant, "mongo_method") + for report in reports: + # Generate hourly batch Ar + cron_body = cron_body + gen_batch_ar(config, tenant, report, "hourly", user, mongo_method) + "\n" + # Generate daily batch Ar + cron_body = cron_body + gen_batch_ar(config, tenant, report, "daily", user, mongo_method) + "\n" + # Generate hourly batch Status + cron_body = cron_body + gen_batch_status(config, tenant, report, "hourly", user, mongo_method) + "\n" + # Generate daily batch Status + cron_body = cron_body + gen_batch_status(config, tenant, report, "daily", user, mongo_method) + "\n" + return cron_body + "\n" + + +def gen_for_all(config, user=None): + """ + For given configuration generate cron jobs for all tenants + Args: + config: obj. Argo configuration + user: str. optional user name + + Returns: str. text snippet with all cron entries for this tenant + """ + cron_body = "" + tenants = config.get("API", "tenants") + for tenant in tenants: + cron_body = cron_body + gen_tenant_all(config, tenant, user) + return cron_body + + +def update_cron_tab(cron_gen_data): + """ + Reads existing cron data from cronfile. Combines existing cron data with + given cron_gen_data. Uses a tempfile to hold the result and calls + crontab to update from this tempfile + + Args: + cron_gen_data: str. generated cron data + """ + + # Try to grab existing cron data (so as to maintain them) + try: + cron_data = check_output(["crontab", "-l"]) + except CalledProcessError: + cron_data = "" + + # If cron data already includes generated entries remove them using signifier + signifier = "\n#The rest are generated by argo engine" + cron_data = cron_data.split(signifier)[0] + + # Append newly generated cron data to the existing + date_stamp = "#The rest are generated by argo engine at {}".format(str(datetime.utcnow())) + cron_data = cron_data + "\n" + date_stamp + "\n\n" + cron_gen_data + + # Open a temp file to write update cron data + tmp_cron_fd, tmp_cron_path = tempfile.mkstemp() + try: + with os.fdopen(tmp_cron_fd, 'w') as tmp_cron: + # write cron data + tmp_cron.write(cron_data) + # make cron update from the new file + tmp_cron.close() + ret_code = check_call(["crontab", tmp_cron_path]) + if ret_code != 0: + log.critical("Could not update cron file") + else: + log.info("Cron file updated successfully") + finally: + pass + os.remove(tmp_cron_path) + + +def run_cron_update(args): + """ + Method to be executed when this is invoked from cli. Reads configuration + and updates crontab for all tenants/jobs + + Args: + args: obj. command line arguments from argparser + """ + + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) + + # Generate cron data + cron_gen = gen_for_all(config) + + # Update cron + update_cron_tab(cron_gen) + + +if __name__ == '__main__': + # Feed Argument parser with the description of the 3 arguments we need + arg_parser = ArgumentParser( + description="update crontab with batch jobs for all tenants/reports") + arg_parser.add_argument( + "-c", "--config", help="config", dest="config", metavar="STRING") + arg_parser.add_argument( + "-u", "--user", help="config", dest="user", metavar="STRING", required=False, default=None) + + # Parse the command line arguments accordingly and introduce them to the run method + sys.exit(run_cron_update(arg_parser.parse_args())) diff --git a/bin/utils/update_profiles.py b/bin/utils/update_profiles.py index 305b3124..e48e490d 100755 --- a/bin/utils/update_profiles.py +++ b/bin/utils/update_profiles.py @@ -3,18 +3,17 @@ import json from snakebite.client import Client from snakebite.errors import FileNotFoundException -from ConfigParser import SafeConfigParser import logging -import logging.handlers import os import uuid from urlparse import urlparse from argparse import ArgumentParser import sys import subprocess +from common import get_log_conf, get_config_paths +from argo_config import ArgoConfig -CONF_ETC = '/etc/argo-streaming/argo-streaming.cfg' -CONF_LOCAL = './argo-streaming.cfg' +log = logging.getLogger(__name__) class ArgoApiClient: @@ -37,10 +36,12 @@ def __init__(self, host, tenant_keys): self.paths.update({ 'reports': '/api/v2/reports', 'operations': '/api/v2/operations_profiles', - 'aggregations': '/api/v2/aggregation_profiles' + 'aggregations': '/api/v2/aggregation_profiles', + 'thresholds': '/api/v2/thresholds_profiles', + 'tenants': '/api/v2/admin/tenants' }) - def get_url(self, resource, item_uuid): + def get_url(self, resource, item_uuid=None): """ Constructs an argo-web-api url based on the resource and item_uuid Args: @@ -79,6 +80,49 @@ def get_resource(self, tenant, url): else: return None + def get_tenants(self, token): + """ + Returns an argo-web-api resource by tenant and url + Args: + token: str. web-api token to be used (for auth) + + Returns: + dict: list of tenants and access keys + + """ + tenants = self.get_admin_resource(token, self.get_url("tenants")) + tenant_keys = dict() + for item in tenants: + for user in item["users"]: + if user["name"].startswith("argo_engine_") and user["api_key"]: + print len(user["api_key"]) + tenant_keys[item["info"]["name"]] = user["api_key"] + return tenant_keys + + @staticmethod + def get_admin_resource(token, url): + """ + Returns an argo-web-api resource by tenant and url + Args: + token: str. admin token to be used + url: str. url of resource + + Returns: + dict: resource contents + + """ + headers = dict() + headers.update({ + 'Accept': 'application/json', + 'x-api-key': token + }) + r = requests.get(url, headers=headers, verify=False) + + if 200 == r.status_code: + return json.loads(r.text)["data"] + else: + return None + def get_profile(self, tenant, report, profile_type): """ Gets an argo-web-api profile by tenant, report and profile type @@ -91,7 +135,13 @@ def get_profile(self, tenant, report, profile_type): dict: profile contents """ + # default recomputation is always an empty json array + if profile_type == "recomputations": + return [] + item_uuid = self.find_profile_uuid(tenant, report, profile_type) + if item_uuid is None: + return None profiles = self.get_resource(tenant, self.get_url(profile_type, item_uuid)) if profiles is not None: return profiles[0] @@ -154,8 +204,13 @@ def find_profile_uuid(self, tenant, report, profile_type): Returns: """ + if profile_type is "aggregations": + profile_type = "aggregation" + report = self.get_report(tenant, self.find_report_uuid(tenant, report)) - for profile in report["profiles"]: + if profile_type == "reports": + return report["id"] + for profile in report["profiles"]: if profile["type"] == profile_type: return profile["id"] @@ -185,7 +240,7 @@ def gen_profile_path(self, tenant, report, profile_type): Args: tenant: str. tenant to be used report: str. report to be used - profile_type: str. profile_type (operations|reports|aggregations) + profile_type: str. profile_type (operations|reports|aggregations|thresholds) Returns: str: hdfs path @@ -195,7 +250,9 @@ def gen_profile_path(self, tenant, report, profile_type): templates.update({ 'operations': '{0}_ops.json', 'aggregations': '{0}_{1}_ap.json', - 'reports': '{0}_{1}_cfg.json' + 'reports': '{0}_{1}_cfg.json', + 'thresholds': '{0}_{1}_thresholds.json', + 'recomputations': 'recomp.json' }) sync_path = self.base_path.replace("{{tenant}}", tenant) @@ -208,7 +265,7 @@ def cat(self, tenant, report, profile_type): Args: tenant: str. tenant name report: str. report name - profile_type: str. profile type (operations|reports|aggregations) + profile_type: str. profile type (operations|reports|aggregations|thresholds) Returns: @@ -227,7 +284,7 @@ def rem(self, tenant, report, profile_type): Args: tenant: str. tenant name report: str. report name - profile_type: str. profile type (operations|reports|aggregations) + profile_type: str. profile type (operations|reports|aggregations|thresholds) Returns: @@ -250,26 +307,33 @@ class ArgoProfileManager: from their latest profile definitions given from argo-web-api. """ - def __init__(self, cfg_path): + def __init__(self, config): """ Initialized ArgoProfileManager which manages updating argo profile files in hdfs Args: - cfg_path: str. path to the configuration file used + config: obj. ArgoConfig object containing the main configuration """ - self.cfg = get_config(cfg_path) - self.log = get_logger("argo-profile-mgr", self.cfg["log_level"]) + self.cfg = config # process hdfs base path - full_path = str(self.cfg["hdfs_sync"]) - full_path = full_path.replace("{{hdfs_host}}", str(self.cfg["hdfs_host"])) - full_path = full_path.replace("{{hdfs_port}}", str(self.cfg["hdfs_port"])) - full_path = full_path.replace("{{hdfs_user}}", str(self.cfg["hdfs_user"])) + namenode = config.get("HDFS", "namenode") + hdfs_user = config.get("HDFS", "user") + full_path = config.get("HDFS", "path_sync") + full_path = full_path.partial_fill(namenode=namenode.geturl(), hdfs_user=hdfs_user) short_path = urlparse(full_path).path - self.hdfs = HdfsReader(self.cfg["hdfs_host"], int(self.cfg["hdfs_port"]), short_path) - self.api = ArgoApiClient(self.cfg["api_host"], self.cfg["tenant_keys"]) + tenant_list = config.get("API", "tenants") + tenant_keys = dict() + # Create a list of tenants -> api_keys + for tenant in tenant_list: + tenant_key = config.get("API", tenant + "_key") + tenant_keys[tenant] = tenant_key + + print namenode.hostname, namenode.port, short_path + self.hdfs = HdfsReader(namenode.hostname, namenode.port, short_path) + self.api = ArgoApiClient(config.get("API", "endpoint").netloc, tenant_keys) def profile_update_check(self, tenant, report, profile_type): """ @@ -282,22 +346,27 @@ def profile_update_check(self, tenant, report, profile_type): """ prof_api = self.api.get_profile(tenant, report, profile_type) - self.log.info("retrieved %s profile(api): %s", profile_type, prof_api) + if prof_api is None: + log.info("profile type %s doesn't exist in report --skipping", profile_type) + return + log.info("retrieved %s profile(api): %s", profile_type, prof_api) + prof_hdfs, exists = self.hdfs.cat(tenant, report, profile_type) - + + if exists: - self.log.info("retrieved %s profile(hdfs): %s ", profile_type, prof_hdfs) + log.info("retrieved %s profile(hdfs): %s ", profile_type, prof_hdfs) prof_update = prof_api != prof_hdfs if prof_update: - self.log.info("%s profile mismatch", profile_type) + log.info("%s profile mismatch", profile_type) else: - self.log.info("%s profiles match -- no need for update", profile_type) + log.info("%s profiles match -- no need for update", profile_type) else: # doesn't exist so it should be uploaded prof_update = True - self.log.info("%s profile doesn't exist in hdfs, should be uploaded", profile_type) + log.info("%s profile doesn't exist in hdfs, should be uploaded", profile_type) # Upload if it's deemed to be uploaded if prof_update: @@ -322,112 +391,106 @@ def upload_profile_to_hdfs(self, tenant, report, profile_type, profile, exists): if exists: is_removed = self.hdfs.rem(tenant, report, profile_type) if not is_removed: - self.log.error("Could not remove old %s profile from hdfs", profile_type) + log.error("Could not remove old %s profile from hdfs", profile_type) return # If all ok continue with uploading the new file to hdfs - hdfs_write_bin = self.cfg["hdfs_writer"] + hdfs_write_bin = self.cfg.get("HDFS", "writer_bin") hdfs_write_cmd = "put" temp_fn = "prof_" + str(uuid.uuid4()) local_path = "/tmp/" + temp_fn with open(local_path, 'w') as outfile: json.dump(profile, outfile) - - hdfs_host = str(self.cfg["hdfs_host"]) + hdfs_host = self.cfg.get("HDFS","namenode").hostname hdfs_path = self.hdfs.gen_profile_path(tenant, report, profile_type) - status = subprocess.check_call([hdfs_write_bin, hdfs_write_cmd, local_path, hdfs_path]) if status == 0: - self.log.info("File uploaded successfully to hdfs host: %s path: %s", hdfs_host, hdfs_path) + log.info("File uploaded successfully to hdfs host: %s path: %s", hdfs_host, hdfs_path) return True else: - self.log.error("File uploaded unsuccessful to hdfs host: %s path: %s", hdfs_host, hdfs_path) + log.error("File uploaded unsuccessful to hdfs host: %s path: %s", hdfs_host, hdfs_path) return False + def upload_tenant_reports_cfg(self, tenant): + reports = self.api.get_reports(tenant) + report_name_list = [] + for report in reports: + + # double check if indeed report belongs to tenant + if report["tenant"] == tenant: + report_name = report["info"]["name"] + report_name_list.append(report_name) + report_uuid = report["id"] + # Set report in configuration + self.cfg.set("TENANTS:"+tenant, "report_" + report_name, report_uuid) + + # update tenant's report name list + self.cfg.set("TENANTS:"+tenant, "reports", ",".join(report_name_list)) + + def upload_tenants_cfg(self): + """ + Uses admin access credentials to contact argo-web-api and retrieve tenant list. + Then updates configuration with tenant list, tenant reports etc. + """ + token = self.cfg.get("API", "access_token") + tenant_keys = self.api.get_tenants(token) + self.api.tenant_keys=tenant_keys + tenant_names = ",".join(tenant_keys.keys()) + + self.cfg.set("API", "tenants", tenant_names) + + # For each tenant update also it's report list + for tenant_name in tenant_keys.keys(): + self.cfg.set("API", tenant_name+"_key", tenant_keys[tenant_name]) + # Update tenant's report definitions in configuration + self.upload_tenant_reports_cfg(tenant_name) + self.upload_tenant_defaults(tenant_name) + + def upload_tenant_defaults(self, tenant): + # check + section_tenant = "TENANTS:"+ tenant + section_metric = "TENANTS:"+ tenant + ":ingest-metric" + mongo_endpoint = self.cfg.get("MONGO","endpoint").geturl() + mongo_uri = self.cfg.get_default(section_tenant,"mongo_uri").fill(mongo_endpoint=mongo_endpoint,tenant=tenant).geturl() + hdfs_user = self.cfg.get("HDFS","user") + namenode = self.cfg.get("HDFS","namenode").netloc + hdfs_check = self.cfg.get_default(section_metric,"checkpoint_path").fill(namenode=namenode,hdfs_user=hdfs_user,tenant=tenant) + + + self.cfg.get("MONGO","endpoint") + + self.cfg.set(section_tenant,"mongo_uri",mongo_uri) + self.cfg.set_default(section_tenant,"mongo_method") + + + self.cfg.set_default(section_metric,"ams_interval") + self.cfg.set_default(section_metric,"ams_batch") + self.cfg.set(section_metric,"checkpoint_path",hdfs_check.geturl()) + self.cfg.set_default(section_metric,"checkpoint_interval") + section_sync = "TENANTS:"+ tenant + ":ingest-sync" + + self.cfg.set_default(section_sync,"ams_interval") + self.cfg.set_default(section_sync,"ams_batch") + section_stream = "TENANTS:"+ tenant + ":stream-status" + + self.cfg.set_default(section_stream,"ams_sub_sync") + self.cfg.set_default(section_stream,"ams_interval") + self.cfg.set_default(section_stream,"ams_batch") + + + + def save_config(self, file_path): + """ + Saves configuration to a specified ini file -def get_config(path): - """ - Creates a specific dictionary with only needed configuration options retrieved from an argo config file - Args: - path: str. path to the generic argo config file to be used - - Returns: - dict: a specific configuration option dictionary with only necessary parameters - - """ - # set up the config parser - config = SafeConfigParser() - - if path is None: - - if os.path.isfile(CONF_ETC): - # Read default etc_conf - config.read(CONF_ETC) - else: - # Read local - config.read(CONF_LOCAL) - else: - config.read(path) - - cfg = dict() - cfg.update(dict(log_level=config.get("LOGS", "log_level"), hdfs_host=config.get("HDFS", "hdfs_host"), - hdfs_port=config.get("HDFS", "hdfs_port"), hdfs_user=config.get("HDFS", "hdfs_user"), - hdfs_sync=config.get("HDFS", "hdfs_sync"), api_host=config.get("API", "host"), - hdfs_writer=config.get("HDFS", "writer_bin"))) - - tenant_list = config.get("API", "tenants").split(",") - tenant_keys = dict() - # Create a list of tenants -> api_keys - for tenant in tenant_list: - tenant_key = config.get("API", tenant + "_key") - tenant_keys[tenant] = tenant_key - cfg["tenant_keys"] = tenant_keys - - return cfg - - -def get_logger(log_name, log_level): - """ - Instantiates a logger - Args: - log_name: str. log name to be used in logger - log_level: str. log level - - Returns: - obj: logger - - """ - # Instantiate logger with proper name - log = logging.getLogger(log_name) - log.setLevel(logging.DEBUG) - - log_level = log_level.upper() - - # Instantiate default levels - levels = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL - } - - # Log to console - stream_log = logging.StreamHandler() - stream_log.setLevel(logging.INFO) - log.addHandler(stream_log) - - # Log to syslog - sys_log = logging.handlers.SysLogHandler("/dev/log") - sys_format = logging.Formatter('%(name)s[%(process)d]: %(levelname)s %(message)s') - sys_log.setFormatter(sys_format) - sys_log.setLevel(levels[log_level]) - log.addHandler(sys_log) - - return log + Args: + file_path: str. path to the configuration file to be saved + """ + self.cfg.save_as(file_path) + log.info("Configuration file %s updated...", file_path) def run_profile_update(args): @@ -438,13 +501,34 @@ def run_profile_update(args): Args: args: obj. command line arguments from arg parser """ - # Set a new profile manager to be used - argo = ArgoProfileManager(args.config) + # Get configuration paths + conf_paths = get_config_paths(args.config) + + # Get logger config file + get_log_conf(conf_paths['log']) + + # Get main configuration and schema + config = ArgoConfig(conf_paths["main"], conf_paths["schema"]) - # check for the following profile types - profile_type_checklist = ["operations", "aggregations", "reports"] - for profile_type in profile_type_checklist: - argo.profile_update_check(args.tenant, args.report, profile_type) + argo = ArgoProfileManager(config) + + if args.tenant is not None: + # check for the following profile types + profile_type_checklist = ["operations", "aggregations", "reports", "thresholds", "recomputations"] + reports = [] + if args.report is not None: + reports.append(args.report) + else: + reports = config.get("TENANTS:"+args.tenant,"reports") + + for report in reports: + for profile_type in profile_type_checklist: + argo.profile_update_check(args.tenant, report, profile_type) + + + else: + argo.upload_tenants_cfg() + argo.save_config(conf_paths["main"]) if __name__ == '__main__': @@ -452,9 +536,9 @@ def run_profile_update(args): arg_parser = ArgumentParser( description="update profiles for specific tenant/report") arg_parser.add_argument( - "-t", "--tenant", help="tenant owner ", dest="tenant", metavar="STRING", required=True) + "-t", "--tenant", help="tenant owner ", dest="tenant", metavar="STRING", required=False, default=None) arg_parser.add_argument( - "-r", "--report", help="report", dest="report", metavar="STRING", required=True) + "-r", "--report", help="report", dest="report", metavar="STRING", required=False, default=None) arg_parser.add_argument( "-c", "--config", help="config", dest="config", metavar="STRING") diff --git a/conf/argo-streaming.conf b/conf/argo-streaming.conf new file mode 100644 index 00000000..3076ac36 --- /dev/null +++ b/conf/argo-streaming.conf @@ -0,0 +1,119 @@ +[HDFS] +namenode=hdfs://localhost:8020 +user= foo +rollback_days= 3 +path_metric= hdfs://{{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/mdata +path_sync= hdfs://{{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/sync +writer_bin= /home/root/hdfs + +[API] +endpoint=https://api_host +access_token=token01 +tenants=TENANT_A,TENANT_B +TENANT_A_key=key1 +TENANT_B_key=key2 + +[MONGO] +endpoint=mongodb://localhost:21017 + +[AMS] +endpoint: test_endpoint:8080 +access_token: foo_token +# Ams proxy +proxy:test_proxy +# Verify ssl +verify: true + +[FLINK] + +path= /path/to/flink +job_manager= http://localhost:8081 + +[JOB-NAMESPACE] +ingest-metric-namespace= Ingesting metric data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}} +ingest-sync-namespace= Ingesting sync data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}} +stream-status-namespace= Streaming status using data {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/[{{ams_sub_metric}}, {{ams_sub_sync}}] + +[CLASSES] +ams-ingest-metric= argo.streaming.AmsIngestMetric +ams-ingest-sync= argo.streaming.AmsIngestSync +batch-ar= argo.batch.ArgoArBatch +batch-status= argo.batch.ArgoStatusBatch +stream-status= argo.streaming.AmsStreamStatus + +[JARS] +ams-ingest-metric= /path/to/ams-ingest-metric-close.jar +ams-ingest-sync= /path/to/ams-ingest-sync-0.1.jar +batch-ar= /path/to/ArgoArBatch-1.0.jar +batch-status= /path/to/ArgoStatusBatch-1.0.jar +stream-status= /path/to/streaming-status-multi2.jar + +[AMS] +ams_endpoint= localhost:8080 +access_token= secret + +[TENANTS:TENANT_A] +ams_project= TENANT_A +ams_token= key_1 +mongo_uri= {{mongo_uri}}/argo_{{tenant}} +mongo_method: insert +reports= report1,report2 +report_report1= uuid1 +report_report2= uuid2 + +[TENANTS:TENANT_A:ingest-metric] +ams_sub= ingest_metrict +ams_interval= 300 +ams_batch= 100 +checkpoint_path= hdfs://localhost:8020/user/foo/check +checkpoint_interval= 30000 + +[TENANTS:TENANT_A:ingest-sync] +ams_sub= ingest_sync +ams_interval= 300 +ams_batch= 100 +checkpoint_path= hdfs://localhost:8020/user/foo/checkt +checkpoint_interval= 30000 + +[TENANTS:TENANT_A:stream-status] +ams_batch= 100 +ams_interval= 100 +ams_sub_metric= status_metric +ams_sub_sync= status_sync +kafka_servers= localhost:9092 +kafka_topic= topic.tenant_a +flink_parallelism= 1 +outputs= kafka + +[TENANTS:TENANT_B] +ams_project= TENANT_B +ams_token= key_1 +mongo_uri= {{mongo_uri}}/argo_{{tenant}} +mongo_method: insert +reports= report1,report2 +report_report1= uuid1 +report_report2= uuid2 + +[TENANTS:TENANT_B:ingest-metric] +ams_sub= ingest_metrict +ams_interval= 300 +ams_batch= 100 +checkpoint_path= hdfs://localhost:8020/user/foo/check +checkpoint_interval= 30000 + +[TENANTS:TENANT_B:ingest-sync] +ams_sub= ingest_sync +ams_interval= 300 +ams_batch= 100 +checkpoint_path= hdfs://localhost:8020/user/foo/checkt +checkpoint_interval= 30000 + +[TENANTS:TENANT_B:stream-status] +ams_batch= 100 +ams_interval= 100 +ams_sub_metric= status_metric +ams_sub_sync= status_sync +kafka_servers= localhost:9092 +kafka_topic= topic.tenant_a +flink_parallelism= 1 +outputs= kafka diff --git a/conf/conf.template b/conf/conf.template index a6f0fcda..1c71731b 100644 --- a/conf/conf.template +++ b/conf/conf.template @@ -1,43 +1,28 @@ [HDFS] # hdfs namenode host -hdfs_host: hdfs_test_host -# hdfs namenode port -hdfs_port: hdfs_test_port +namenode: hdfs://hdfs_test_host:hdfs_test_host # hdfs user -hdfs_user: hdfs_test_user +user: hdfs_test_user # hdfs rollback days: rollback_days: 3 # hdfs path for metric data -hdfs_metric: hdfs://{{hdfs_host}}:{{hdfs_port}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/mdata +path_metric: {{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/mdata # hdfs path for sync data -hdfs_sync: hdfs://{{hdfs_host}}:{{hdfs_port}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/sync +path_sync: {{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/sync # hdfs writer executable writer_bin: /path/to/binary [API] -host = api.foo -tenants = TA,TB,TC -TA_key = key1 -TB_key = key2 -TC_key = key3 +endpoint = api.foo +tenants = TENANTA +access_token = key0 +TENANTA_key = key1 +TENANTB_key = key2 +TENANTC_key = key3 - -[LOGS] -# log handlers to support -log_modes: console,syslog,file -# global log level -log_level: info -# specific level for syslog handler -syslog_level: critical -# specific level for file handler -file_level: warning -# specific level for console handler -console_level: info -# socket for syslog handler -syslog_socket: /dev/log -# path for file handler -file_path: +[MONGO] +endpoint = mongodb://localhost:21017 [FLINK] # path for flink executable @@ -47,11 +32,11 @@ job_manager: [JOB-NAMESPACE] # Template to check if a metric job with similar name already runs -ingest-metric-namespace: Ingesting data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}} +ingest-metric-namespace: Ingesting data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{ams_project}}/subscriptions/{{ams_sub}} # Template to check if a sync job with similar name already runs -ingest-sync-namespace: Ingesting sync data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}} +ingest-sync-namespace: Ingesting sync data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{ams_project}}/subscriptions/{{ams_sub}} #Template to check if a stream status job with similar name already runs -stream-status-namespace: Streaming status using data {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/[{{ams_sub_metric}}, {{ams_sub_sync}}] +stream-status-namespace: Streaming status using data {{ams_endpoint}}:{{ams_port}}/v1/projects/{{ams_project}}/subscriptions/[{{ams_sub_metric}}, {{ams_sub_sync}}] [CLASSES] # Specify class to run during job submit @@ -71,36 +56,24 @@ stream-status: test.jar [AMS] -# AMS service port -ams_port: test_port -# Ams service endpoint -ams_endpoint: test_endpoint +endpoint: https://test_endpoint:8080 +access_token: foo_token # Ams proxy -proxy_enabled: true -ams_proxy:test_proxy +proxy:test_proxy # Verify ssl -ssl_enabled: true +verify: true # Tenant General Configuration [TENANTS:TENANTA] # Map tenant to AMS project -ams_project:test_project +ams_project: test_project # Tenant's AMS token -ams_token:test_token - -# report names per tenant -[TENANTS:TENANTA:REPORTS] -# report UUID -report_name : report_uuid +ams_token: test_token +reports: report_name +report_report_name: report_uuid +mongo_uri: {{mongo_endpoint}}/argo_{{tenant}} +mongo_method: insert -# tenant specific mongo configuration -[TENANTS:TENANTA:MONGO] -# mongo host -mongo_host: mongo_test_host -# mongo port -mongo_port: mongo_test_port -# mongo uri for batch ar -mongo_uri: mongodb://{{mongo_host}}:{{mongo_port}}/argo_TENANTA # Tenant-job specific configuration [TENANTS:TENANTA:ingest-metric] @@ -120,33 +93,30 @@ ams_interval: 3000 ams_batch: 100 [TENANTS:TENANTA:stream-status] # whether or not it should also include mongo information(it will access the section TENANTS:TENANTA:MONGO) -use_mongo:true mongo_method:upsert -ams_batch: test_batch -ams_interval: test_interval +ams_batch: 10 +ams_interval: 300 # ARGO messaging subscription to pull metric data from -ams.sub.metric: metric_status +ams_sub_metric: metric_status # ARGO messaging subscription to pull sync data from -ams.sub.sync: sync_status +ams_sub_sync: sync_status #hbase endpoint -hbase.master: hbase.devel -# hbase master port -hbase.master.port: test_hbase_port +hbase_master: hbase://hbase.devel:8080 # comma separated list of hbase zookeeper servers -hbase.zk.quorum: test_zk_servers +hbase_zk_quorum: test_zk_servers # port used by hbase zookeeper servers -hbase.zk.port: test_zk_port +hbase_zk_port: 8080 # table namespace used (usually tenant name) -hbase.namespace: test_hbase_namespace +hbase_namespace: test_hbase_namespace # table name (usually metric_data) -hbase.table: metric_data +hbase_table: metric_data # Kafka server list to connect to -kafka.servers: test_kafka_servers +kafka_servers: kafka_server:9090,kafka_server2:9092 # Kafka topic to send status events to -kafka.topic: test_kafka_topic +kafka_topic: test_kafka_topic # filesystem path for output (prefix with "hfds://" for hdfs usage) -fs.output: test_fs_output +fs_output: test_fs_output # flink parallelism for specific tenant and job(execution environment level) flink_parallelism: 1 # different output destinations for the results -outputs: hbase,kafka,fs +outputs: hbase,kafka,fs,mongo diff --git a/conf/config.schema.json b/conf/config.schema.json new file mode 100644 index 00000000..30536502 --- /dev/null +++ b/conf/config.schema.json @@ -0,0 +1,307 @@ +{ + "HDFS": { + "namenode": { + "desc": "endpoint host of namenode", + "type": "uri" + }, + "path_metric": { + "desc": "template for constructing the hdfs path to tenants metric data location", + "type": "template,uri", + "default": "hdfs://{{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/mdata" + }, + "path_sync": { + "desc": "template for constructing the hdfs path to tenants sync data location", + "type": "template,uri", + "default": "hdfs://{{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/sync" + }, + "user": { + "desc": "name of the hdfs user", + "type": "string", + "default": "root" + }, + "rollback_days": { + "desc": "how many days to roll back if a sync file is missing", + "type": "int", + "default": "3" + }, + "writer_bin": { + "desc": "path to hdfs binary used for uploading files in hdfs", + "type": "path", + "default": "/root/hdfs" + } + }, + + "MONGO": { + "endpoint": { + "desc": "mongodb core endpoint", + "type": "uri" + } + }, + + "AMS": { + "endpoint": { + "desc": "argo messaging endpoint", + "type": "uri" + }, + "access_token": { + "desc": "argo messaging access token", + "type": "string" + }, + "proxy": { + "desc": "ams proxy to be used", + "type": "uri", + "optional": true, + "default": "http://localhost:3128" + }, + "verify":{ + "desc":"ssl verify ams endpoint", + "type":"bool", + "optional": true, + "default": "true" + } + }, + "API": { + "endpoint": { + "desc": "argo-web-api endpoint", + "type": "uri" + }, + "access_token": { + "desc": "token for argo-web-api access", + "type": "string" + }, + "tenants": { + "desc": "list of tenants", + "type": "list" + }, + "~_key": { + "~": "tenants", + "desc": "tenants key", + "type": "string" + } + }, + "FLINK": { + "path": { + "desc": "path to flink executable", + "type": "path" + }, + "job_manager": { + "desc": "url of flink's job manager", + "type": "uri" + } + }, + "JOB-NAMESPACE": { + "ingest-metric-namespace": { + "desc": "template for naming ingest metric jobs in flink", + "type": "template", + "default": "Ingesting metric data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}}" + }, + "ingest-sync-namespace": { + "desc": "template for naming ingest sync jobs in flink", + "type": "template", + "default": "Ingesting sync data from {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/{{ams_sub}}" + }, + "stream-status-namespace": { + "desc": "template for naming stream status jobs in flink", + "type": "template", + "default": "Streaming status using data {{ams_endpoint}}:{{ams_port}}/v1/projects/{{project}}/subscriptions/[{{ams_sub_metric}}, {{ams_sub_sync}}]" + } + }, + "CLASSES": { + "ams-ingest-metric": { + "desc": "class name for entry point in ingest metric flink job jar", + "type": "string", + "default": "argo.streaming.AmsIngestMetric" + }, + "ams-ingest-sync": { + "desc": "class name for entry point in ingest sync flink job jar", + "type": "string", + "default": "argo.streaming.AmsIngestSync" + }, + "batch-ar": { + "desc": "class name for entry point in batch ar flink job jar", + "type": "string", + "default": "argo.batch.ArgoArBatch" + }, + "batch-status": { + "desc": "class name for entry point in batch status flink job jar", + "type": "string", + "default": "argo.batch.ArgoArBatch" + }, + "stream-status": { + "desc": "class name for entry point in stream status flink job jar", + "type": "string", + "default": "argo.batch.ArgoStatusBatch" + } + }, + "JARS": { + "ams-ingest-metric": { + "desc": "class name for entry point in ingest metric flink job jar", + "type": "path" + }, + "ams-ingest-sync": { + "desc": "class name for entry point in ingest sync flink job jar", + "type": "path" + }, + "batch-ar": { + "desc": "class name for entry point in batch ar flink job jar", + "type": "path" + }, + "batch-status": { + "desc": "class name for entry point in batch status flink job jar", + "type": "path" + }, + "stream-status": { + "desc": "class name for entry point in stream status flink job jar", + "type": "path" + } + }, + + "TENANTS:~":{ + "~":"API.tenants", + "reports":{ + "desc": "available reports for tenant", + "type": "list" + }, + "report_~": { + "desc": "reports uuid", + "type": "string", + "~": "reports" + }, + "ams_project": { + "desc": "ams project used for this tenant", + "type": "string" + }, + "ams_token": { + "desc": "ams token used for this tenant", + "type": "string" + }, + "mongo_uri": { + "desc": "mongo uri including host port and database", + "type": "template,uri", + "default": "{{mongo_endpoint}}/argo_{{tenant}}" + }, + "mongo_method": { + "desc": "default method used in mongodb operations", + "type": "string", + "default": "insert" + } + }, + "TENANTS:~:ingest-metric":{ + "~":"API.tenants", + "ams_sub":{ + "desc": "subscription for ingesting metric data", + "type": "string" + }, + "ams_interval":{ + "desc": "interval for polling ams for metric data", + "type": "long", + "default": "300" + }, + "ams_batch":{ + "desc": "number of messages to retrieve at once from ams", + "type": "long", + "default": "100" + }, + "checkpoint_path": { + "desc": "hdfs checkpoint path", + "type": "template,uri", + "default": "hdfs://{{namenode}}/user/{{hdfs_user}}/argo/tenants/{{tenant}}/checkp" + }, + "checkpoint_interval": { + "desc": "interval between hdfs checkpoints", + "type": "long", + "default": "30000" + } + }, + "TENANTS:~:ingest-sync":{ + "~":"API.tenants", + "ams_sub":{ + "desc": "subscription for ingesting sync data", + "type": "string" + }, + "ams_interval":{ + "desc": "interval for polling ams for sync data", + "type": "long", + "default": "3000" + }, + "ams_batch":{ + "desc": "number of messages to retrieve at once from ams", + "type": "long", + "default": "10" + } + }, + "TENANTS:~:stream-status":{ + "~":"API.tenants", + "ams_sub_metric":{ + "desc": "subscription for ingesting metric data", + "type": "string" + }, + "ams_sub_sync":{ + "desc": "subscription for ingesting sync data", + "type": "string" + }, + "ams_interval":{ + "desc": "interval for polling ams for sync data", + "type": "long", + "default": "300" + }, + "ams_batch":{ + "desc": "number of messages to retrieve at once from ams", + "type": "long", + "default": "100" + }, + + "hbase_master":{ + "desc": "hbase master node uri hbase://localhost:9090", + "type": "uri", + "optional": true + }, + "hbase_zk_quorum":{ + "desc": "comma-separated list of kafka servers", + "type": "list", + "optional": true + }, + "hbase_zk_port":{ + "desc": "hbase zookeeper port", + "type": "long", + "optional": true + }, + "hbase_table":{ + "desc": "hbase table used", + "type": "string", + "optional": true + }, + "hbase_namespace":{ + "desc": "hbase namespace used ", + "type": "string", + "optional": true + }, + "kafka_servers":{ + "desc": "comma-separated list of kafka servers to send messages to", + "type": "list", + "optional": true + }, + "kafka_topic":{ + "desc": "kafka topic to directly write to", + "type": "string", + "optional": true + }, + "mongo_method": { + "desc": "method on how to insert data in mongo (insert|upsert)", + "type": "string", + "optional": true + }, + "flink_parallelism":{ + "desc": "number of parallelism to be used in flink", + "type": "int", + "optional": true + }, + "outputs":{ + "desc": "list of output destinations", + "type": "list", + "optional": true + } + + } + +} diff --git a/conf/logger.conf b/conf/logger.conf new file mode 100644 index 00000000..10da04f9 --- /dev/null +++ b/conf/logger.conf @@ -0,0 +1,28 @@ +[loggers] +keys=root + +[logger_root] +level=INFO +handlers=consoleHandler,syslogHandler + +[formatters] +keys=simpleFormatter + +[formatter_simpleFormatter] +format=%(asctime)s %(name)s %(levelname)s %(message)s +datefmt= + +[handlers] +keys=consoleHandler, syslogHandler + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[handler_syslogHandler] +class=handlers.SysLogHandler +level=WARNING +formatter=simpleFormatter +args=(('localhost', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_USER) diff --git a/docs/check_tenant.md b/docs/check_tenant.md new file mode 100644 index 00000000..ef9d3592 --- /dev/null +++ b/docs/check_tenant.md @@ -0,0 +1,62 @@ +# Python script for checking tenant configuration (in engine, hdfs and ams) + +The specific script reads tenant configuration (as defined in argo-engine global config file) +and checks if the tenant has been properly configured in hdfs destination (computation profiles deployed, folders present) +and in ams (topics and subscriptions established.) Also checks the incoming data status for e.g. + +- The number of messages in hdfs +- And if latest metric and sync data are present in hdfs directories + +To check for a specific tenant issue: +`./bin/utils/check_tenant.py -t FOO -d 2018-09-10 -c /path/to/argo-streaming.conf` + +To check for a specific tenant for today's date issue: +`./bin/utils/check_tenant.py -t FOO -c /path/to/argo-streaming.conf` + +To check for all tenants for today's date issue: +`./bin/utils/check_tenant.py -c /path/to/argo-streaming.conf` + +# Report format +Script produces a status report in json format for each tenant which is automatically uploaded to the argo-web-api service +```json +{ + "last_check": "2018-09-10T13:58:14Z", + "engine_config": true, + "hdfs": { + "metric_data": true, + "sync_data": { + "Critical": { + "downtimes": true, + "group_endpoints": true, + "blank_recomputation": true, + "configuration_profile": true, + "group_groups": true, + "weights": true, + "operations_profile": true, + "metric_profile": true, + "aggregation_profile": true + } + } + }, + "ams": { + "metric_data": { + "ingestion": true, + "status_streaming": true, + "publishing": true, + "messages_arrived": 1774700 + }, + "sync_data": { + "ingestion": true, + "status_streaming": true, + "publishing": true, + "messages_arrived": 715 + } + } +} +``` + +- `engine_config`: reports if the tenant configuration in argo engine is valid +- `last_check`: reports the timestamp of the check performed +- `tenant`: refers tto the tenant name +- `hdfs`: contains status check results for the tenant in the HDFS datastore +- `ams`: contains status checks for the tenant in the Argo Messaging Service \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..6a4e8dc5 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,94 @@ +#Argo Streaming Engine Main configuration + +Argo Streaming Engine's main configuration is maintained in a single ini file +located by default in `/etc/argo-streaming/argo-streaming.conf` + +Argo Streaming configuration is extensible and includes iteratable sections +for each tenant + +## Configuration sections +Main configuration includes the following sections - (some of them are iteratable) + +###[HDFS] +Connectivity to hdfs cluster for storing metric and connector data + +###[API] +Connectivity to argo-web-api +Supported tenants are specified here + +###[FLINK] +Connectivity to the flink cluster + +###[JARS] +Specify jar paths used for each computation/job type + +###[CLASSES] +Specify classes in each jar used for each computation/job type + +###[AMS] +Connectivity to Argo Messaging Cluster for ingesting data and storing events + +###[TENANTS:FooTenant] +Specific section with parameters for FooTenant +Tenant's AMS specifics (project, credentials) and MongoDB destinations. +Also Tenant reports are specified here + +###[TENANTS:FooTenant:ingest-metric] +Specific parameters for FooTenant's ingest-metric jobs. +AMS subscription parameters and ingestion rates. + +###[TENANTS:FooTenant:ingest-sync] +Specific parameters for FooTenant's ingest-metric jobs. +AMS subscription parameters and ingestion rates. + +###[TENANTS:FooTenant:stream-status] +Specific parameters for FooTenant's stream-status jobs. +AMS subscription parameters and ingestion rates. +Status event output destinations (kafka, hbase, fs) + +## Configuration consistency validation and schema +To maintain consistency in main configuration file especially in the expandable sections (tenants) +a configuration schema accompanies the file in the same folder (`/etc/argo-streaming/config.schema.json`) + +Schema is a json structure that describes the expected configuration sections and fields. For each field +an expected type is specified, along with a verbose description and an optional flag. For sections or fields +that are bound to iterate (such as tenant sections) a special iteration symbol `~` is used in names. Also +the iterable item has always an `~` parameter which specifies the list of items that are used for iterating + +For example the `API.tenants` holds a list of available tenants `ta`,`tb`,`tc`. The specific +section for each tenant is declared with the schema name `TENANTS:~` and it's iteration field +`"~" : API.tenants` dictates that the the following sections will be generated based on `TENANTS:~` name +and the list `API.tenants` items: `ta`, `tb`, `tc` such as: +- `TENANT:ta` +- `TENANT:tb` +- `TENANT:tc` + +A snippet of the schema configuration defining the above is provided below: +``` +{ + "API": { + "tenants": { + "type": "list", + "desc": "a list of tenants provided by the argo-web-api and supported in argo-streaming engine" + } + }, + "TENANT:~": { + "~" : "API.tenants" + "ams_project": { + type: "string", + "desc": "argo-messaging server project for this tenant" + }, + "mongo_uri": { + type: "uri", + "desc": "mongodb destination for storing tenant's results" + } + } +} +``` + +## Logging configuration +Logging in Argo Streaming Service is handled by a third configuration file in json format deployed +in the same folder as main configuration `/etc/argo-streaming/logger.conf` + +Logging.conf uses python's logging configuration file format as described +here: https://docs.python.org/2/library/logging.config.html#logging-config-fileformat diff --git a/docs/recomputations.md b/docs/recomputations.md new file mode 100644 index 00000000..644d9c79 --- /dev/null +++ b/docs/recomputations.md @@ -0,0 +1,9 @@ +# Python script for retrieving recomputations + +The specific script reads recomputatation information from mongodb for a specific tenant, report and date. +Each relevant recomputation retrieved is compiled in a json list and stored as a file in hdfs + +To retrieve recomputations for tenant FOO report Critical and date 2018-09-10 issue: +`./bin/utils/recomputations.py -t FOO -r Critical -d 2018-09-10 -c /path/to/argo-streaming.conf` + +Recomputation retrieval is also automatically called during batch job submission \ No newline at end of file diff --git a/docs/submission-scripts.md b/docs/submission-scripts.md index e21afe4c..02c80866 100644 --- a/docs/submission-scripts.md +++ b/docs/submission-scripts.md @@ -52,6 +52,10 @@ A/R job submission is a batch job that will run and finish on the cluster `-m : How mongoDB will handle the generated results. Either insert or upsert` +`--profile-check: (optional) Check if profiles used in computation are out of date and update them` + +`--thresholds: (optional) Check if threshold rules are defined and use them during computations` + ## Status Batch Job Status job submission is a batch job that will run and finish on the cluster @@ -70,6 +74,10 @@ Status job submission is a batch job that will run and finish on the cluster `-m : How mongoDB will handle the generated results. Either insert or upsert` +`--profile-check: (optional) Check if profiles used in computation are out of date and update them` + +`--thresholds: (optional) Check if threshold rules are defined and use them during computations` + ## Status Stream Job Status streaming job receives metric and sync data from AMS calculates and generates status events which are forwarded to kafka @@ -100,9 +108,6 @@ Status streaming job receives metric and sync data from AMS calculates and gener [HDFS] HDFS credentials -[LOGS] -log modes describe what kind of logging handlers we want to use. For each handler we specify its logging level and resource. If there is no specified level for each handler, we specify a global log level for all handlers. - [FLINK] Specify where to find the flink executable and the job manager. @@ -114,14 +119,10 @@ AMS port to connect to and endpoint where you find the service.Also whether or n [TENANTS:] Token for each tenant to access the service +Avaliable tenant reports +Tenant Mongo connectivity -[TENANTS:TENANTA:REPORTS] -the reports' UUIDs for the respective tenant - -[TENANTS:TENANTA:MONGO] -Mongo information, specific to each respective tenant - -[TENANTS:TENANTA:JOB] +[TENANTS:TENANTA:JOB-TYPE] Specific parameters needed for each job to run ams_batch : num of messages to be retrieved per request to AMS service ams_interval : interval (in ms) between AMS service requests @@ -129,7 +130,7 @@ check_interval : interval for checkpointing (in ms) check_path : path to store flink checkpoints flink_parallelism: execution environment level ---Specific for stream-status job--- -use_mongo: Whether or not it should include mongo information +mongo_method: input method for mongo, when mongo is specified in outputs outputs: the possible output dstination sepaarted by comma. For each destination, its respective information should also be specififed. FOR the hbase output we need, the hbase endpoint, the hbase endpoint port, the zookeeper servers(comma separated list), the port used by the servers and the table namespace. FOR the kafka output we need the the kafka servers(comma separated list), and the topic. diff --git a/docs/update_ams.md b/docs/update_ams.md new file mode 100644 index 00000000..8d159463 --- /dev/null +++ b/docs/update_ams.md @@ -0,0 +1,41 @@ +# Python script for updating ams (argo-web-api --> argo streaming engine --> ams) + +Having an updated configuration from argo-web-api, argo engine can access Argo +Messaging service to check if expected projects/topics/subscriptions/users have been +setup for each tenant. If items are missing update_ams script can create them (by + setting up new tenant project, topics, subscriptions and users) + +To manually trigger an argo ams check for all tenants, issue: +`./bin/utils/update_ams.py -c /path/to/argo-streaming.conf` + +To manually trigger an argo ams check for a specific tenant issue: +`./bin/utils/update_ams.py -c /path/to/argo-streaming.conf -t tenant_a` + +Script will read the designated `argo-streaming.conf` and extract information +for all supported Tenants. For each tenant will examine if the corresponding +project exists in AMS endpoint and if the needed topics for publishing and +ingestion have been set-up. + +# Expected AMS items for each tenant + +Given a tenant with name 'foo' the engine expects the following to be set-up +in AMS: +- tenant-project: `FOO` (project always has same name as tenant but in all caps) +- tenant-topics: + - `metric_data` (feed for metric data) + - `sync_data` (feed for connector data) +- tenant-subscriptions: + - `ingest_metric` (connected to `metric_data` used for ingestion) + - `ingest_sync` (connected to `sync_data` used for ingestion) + - `status_metric` (connected to `metric_data` used for streaming computations) + - `status_sync` (connected to `metric_data` used for streaming computations) + - users: + - `ams_foo_admin` with role: `project_admin` (project admin) + - `ams_foo_publisher` with role `publisher` (used for publishing clients) + - `ams_foo_consumer`with role `consumer` (used for ingestion jobs) + +Also the topics must include `ams_foo_publisher` in their acls and in the same way +subscriptions must include `ams_foo_consumer` in their acls + +Update_ams for a given tenant, checks AMS endpoint for the above expected items. +If an item is found missing it's automatically created. diff --git a/docs/update_cron.md b/docs/update_cron.md new file mode 100644 index 00000000..fdfbc477 --- /dev/null +++ b/docs/update_cron.md @@ -0,0 +1,25 @@ +# Python script for updating crontab (argo-web-api --> crontab) + +Having an updated configuration from argo-web-api, argo engine can automatically +create all necessary crontab entries for batch a/r and status jobs covering +all tenants and all reports. + +To manually trigger a crontab update (using latest engine configuration) issue: +`./bin/utils/update_cron.py -c /path/to/argo-streaming.conf` + +Script will read the designated `argo-streaming.conf` and extract information +for all supported Tenants and reports. For each tenant and report will generate +a combination of daily and hourly crontab entries for scheduling a/r and status +job submissions. + +After running the script a new section will be appended at the end of the crontab +beginning with the line as seen below: + +``` +... +#The rest are generated by argo engine at 2018-07-17 09:35:24.787980 +#Jobs for TENANT_A + +#TENANT_A:report1 hourly A/R +... +``` diff --git a/docs/update_engine.md b/docs/update_engine.md new file mode 100644 index 00000000..daa724b7 --- /dev/null +++ b/docs/update_engine.md @@ -0,0 +1,26 @@ +# Python script for updating argo-engine + +The specific script triggers a compete update of argo-engine configuration, +tenant profiles, ams definitions and cron-jobs by requesting tenant information +from argo-web-api. + +## Update steps +The script executes in-order the following update steps: + - Update argo-engine configuration from argo-web-api + - Check AMS and create missing topics, subscriptions, users. Update back argo-engine configuration + - Update for each tenant/report the required profiles in HDFS + - Check tenant status for each tenant and report + - For valid reports (reports that have ready data on hdfs) generate cron jobs + - Update crontab for the specified cron jobs + +## Backup of configuration +Script gives the ability to backup the current configuration before update (Use the `-b cli argument`). +The configuration will be backed up in the form of `/path/to/argo-engine.conf.2018-06-06T00:33:22` + +# Execution +To run the script issue: +`./update-egine.py -c /etc/argo-engine.conf -b` + +Arguments: +`-c`: specify the path to argo-engine main configuration file +`-b`: (optional) backup the current configuration diff --git a/docs/update_profiles.md b/docs/update_profiles.md index 38d328d8..77ff0a29 100644 --- a/docs/update_profiles.md +++ b/docs/update_profiles.md @@ -9,6 +9,8 @@ automatically by connectors. Profiles include: applied on different service levels - report configuration profile: `TENANT_REPORT_cfg.json` which includes information on the report it self, what profiles it uses and how filters data +- threhsolds_profile (optional): `TENANT_REPORT_thresholds.json` which includes thresholds rules to be applied during computation + Each report uses an operations profile. The operation profile is defined also in argo-web-api instance at the following url `GET https://argo-web-api.host.example/api/v2/operations_profiles/{{profile_uuid}}` @@ -16,10 +18,14 @@ Each report uses an operations profile. The operation profile is defined also in Each report uses an aggregation profile. The aggregation profile is defined also in argo-web-api instance at the following url `GET https://argo-web-api.host.example/api/v2/aggregation_profiles/{{profile_uuid}}` +Each report optionally contains a thresholds profile. The thresholds profile is defined also in argo-web-api instance at the following url +`GET https://argo-web-api.host.example/api/v2/thresholds_profiles/{{profile_uuid}}` + Each report contains a configuration profile. The report is defined also in argo-web-api instance at the following url `GET https://argo-web-api.host.example/api/v2/reports/{{report_uuid}}` + Providing a specific `tenant` and a specific `report`, script `update_profiles` checks corresponding profiles on hdfs against latest profiles provided by argo-web-api. If they don't match it uploads the latest argo-web-api profile definition in hdfs @@ -76,9 +82,6 @@ tenants : TENANT_A, TENANT_B TENANT_A_key = secret1 TENANT_B_key = secret2 -[LOGS] -# get log level -log_level: info ``` # Dependencies diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricData.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricData.java index 6868f656..77800770 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricData.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricData.java @@ -1,13 +1,17 @@ /** * Autogenerated by Avro - * + * * DO NOT EDIT DIRECTLY */ -package argo.avro; +package argo.avro; + +import org.apache.avro.specific.SpecificData; + @SuppressWarnings("all") @org.apache.avro.specific.AvroGenerated public class MetricData extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { - public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); + private static final long serialVersionUID = 3861438289744595870L; + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"actual_data\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"default\":null},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } @Deprecated public java.lang.String timestamp; @Deprecated public java.lang.String service; @@ -15,32 +19,46 @@ public class MetricData extends org.apache.avro.specific.SpecificRecordBase impl @Deprecated public java.lang.String metric; @Deprecated public java.lang.String status; @Deprecated public java.lang.String monitoring_host; + @Deprecated public java.lang.String actual_data; @Deprecated public java.lang.String summary; @Deprecated public java.lang.String message; @Deprecated public java.util.Map tags; /** - * Default constructor. + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use newBuilder(). */ public MetricData() {} /** * All-args constructor. + * @param timestamp The new value for timestamp + * @param service The new value for service + * @param hostname The new value for hostname + * @param metric The new value for metric + * @param status The new value for status + * @param monitoring_host The new value for monitoring_host + * @param actual_data The new value for actual_data + * @param summary The new value for summary + * @param message The new value for message + * @param tags The new value for tags */ - public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String summary, java.lang.String message, java.util.Map tags) { + public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String actual_data, java.lang.String summary, java.lang.String message, java.util.Map tags) { this.timestamp = timestamp; this.service = service; this.hostname = hostname; this.metric = metric; this.status = status; this.monitoring_host = monitoring_host; + this.actual_data = actual_data; this.summary = summary; this.message = message; this.tags = tags; } public org.apache.avro.Schema getSchema() { return SCHEMA$; } - // Used by DatumWriter. Applications should not call. + // Used by DatumWriter. Applications should not call. public java.lang.Object get(int field$) { switch (field$) { case 0: return timestamp; @@ -49,13 +67,15 @@ public java.lang.Object get(int field$) { case 3: return metric; case 4: return status; case 5: return monitoring_host; - case 6: return summary; - case 7: return message; - case 8: return tags; + case 6: return actual_data; + case 7: return summary; + case 8: return message; + case 9: return tags; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } - // Used by DatumReader. Applications should not call. + + // Used by DatumReader. Applications should not call. @SuppressWarnings(value="unchecked") public void put(int field$, java.lang.Object value$) { switch (field$) { @@ -65,15 +85,17 @@ public void put(int field$, java.lang.Object value$) { case 3: metric = (java.lang.String)value$; break; case 4: status = (java.lang.String)value$; break; case 5: monitoring_host = (java.lang.String)value$; break; - case 6: summary = (java.lang.String)value$; break; - case 7: message = (java.lang.String)value$; break; - case 8: tags = (java.util.Map)value$; break; + case 6: actual_data = (java.lang.String)value$; break; + case 7: summary = (java.lang.String)value$; break; + case 8: message = (java.lang.String)value$; break; + case 9: tags = (java.util.Map)value$; break; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } /** * Gets the value of the 'timestamp' field. + * @return The value of the 'timestamp' field. */ public java.lang.String getTimestamp() { return timestamp; @@ -89,6 +111,7 @@ public void setTimestamp(java.lang.String value) { /** * Gets the value of the 'service' field. + * @return The value of the 'service' field. */ public java.lang.String getService() { return service; @@ -104,6 +127,7 @@ public void setService(java.lang.String value) { /** * Gets the value of the 'hostname' field. + * @return The value of the 'hostname' field. */ public java.lang.String getHostname() { return hostname; @@ -119,6 +143,7 @@ public void setHostname(java.lang.String value) { /** * Gets the value of the 'metric' field. + * @return The value of the 'metric' field. */ public java.lang.String getMetric() { return metric; @@ -134,6 +159,7 @@ public void setMetric(java.lang.String value) { /** * Gets the value of the 'status' field. + * @return The value of the 'status' field. */ public java.lang.String getStatus() { return status; @@ -149,6 +175,7 @@ public void setStatus(java.lang.String value) { /** * Gets the value of the 'monitoring_host' field. + * @return The value of the 'monitoring_host' field. */ public java.lang.String getMonitoringHost() { return monitoring_host; @@ -162,8 +189,25 @@ public void setMonitoringHost(java.lang.String value) { this.monitoring_host = value; } + /** + * Gets the value of the 'actual_data' field. + * @return The value of the 'actual_data' field. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value the value to set. + */ + public void setActualData(java.lang.String value) { + this.actual_data = value; + } + /** * Gets the value of the 'summary' field. + * @return The value of the 'summary' field. */ public java.lang.String getSummary() { return summary; @@ -179,6 +223,7 @@ public void setSummary(java.lang.String value) { /** * Gets the value of the 'message' field. + * @return The value of the 'message' field. */ public java.lang.String getMessage() { return message; @@ -194,6 +239,7 @@ public void setMessage(java.lang.String value) { /** * Gets the value of the 'tags' field. + * @return The value of the 'tags' field. */ public java.util.Map getTags() { return tags; @@ -207,21 +253,32 @@ public void setTags(java.util.Map value) { this.tags = value; } - /** Creates a new MetricData RecordBuilder */ + /** + * Creates a new MetricData RecordBuilder. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder() { return new argo.avro.MetricData.Builder(); } - - /** Creates a new MetricData RecordBuilder by copying an existing Builder */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData.Builder other) { return new argo.avro.MetricData.Builder(other); } - - /** Creates a new MetricData RecordBuilder by copying an existing MetricData instance */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing MetricData instance. + * @param other The existing instance to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData other) { return new argo.avro.MetricData.Builder(other); } - + /** * RecordBuilder for MetricData instances. */ @@ -234,23 +291,70 @@ public static class Builder extends org.apache.avro.specific.SpecificRecordBuild private java.lang.String metric; private java.lang.String status; private java.lang.String monitoring_host; + private java.lang.String actual_data; private java.lang.String summary; private java.lang.String message; private java.util.Map tags; /** Creates a new Builder */ private Builder() { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); } - - /** Creates a Builder by copying an existing Builder */ + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ private Builder(argo.avro.MetricData.Builder other) { super(other); + if (isValidValue(fields()[0], other.timestamp)) { + this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.service)) { + this.service = data().deepCopy(fields()[1].schema(), other.service); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.hostname)) { + this.hostname = data().deepCopy(fields()[2].schema(), other.hostname); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.metric)) { + this.metric = data().deepCopy(fields()[3].schema(), other.metric); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.status)) { + this.status = data().deepCopy(fields()[4].schema(), other.status); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.monitoring_host)) { + this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); + fieldSetFlags()[5] = true; + } + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); + fieldSetFlags()[6] = true; + } + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); + fieldSetFlags()[7] = true; + } + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); + fieldSetFlags()[8] = true; + } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - - /** Creates a Builder by copying an existing MetricData instance */ + + /** + * Creates a Builder by copying an existing MetricData instance + * @param other The existing instance to copy. + */ private Builder(argo.avro.MetricData other) { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); if (isValidValue(fields()[0], other.timestamp)) { this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); fieldSetFlags()[0] = true; @@ -275,242 +379,411 @@ private Builder(argo.avro.MetricData other) { this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); fieldSetFlags()[5] = true; } - if (isValidValue(fields()[6], other.summary)) { - this.summary = data().deepCopy(fields()[6].schema(), other.summary); + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); fieldSetFlags()[6] = true; } - if (isValidValue(fields()[7], other.message)) { - this.message = data().deepCopy(fields()[7].schema(), other.message); + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); fieldSetFlags()[7] = true; } - if (isValidValue(fields()[8], other.tags)) { - this.tags = data().deepCopy(fields()[8].schema(), other.tags); + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); fieldSetFlags()[8] = true; } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - /** Gets the value of the 'timestamp' field */ + /** + * Gets the value of the 'timestamp' field. + * @return The value. + */ public java.lang.String getTimestamp() { return timestamp; } - - /** Sets the value of the 'timestamp' field */ + + /** + * Sets the value of the 'timestamp' field. + * @param value The value of 'timestamp'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTimestamp(java.lang.String value) { validate(fields()[0], value); this.timestamp = value; fieldSetFlags()[0] = true; - return this; + return this; } - - /** Checks whether the 'timestamp' field has been set */ + + /** + * Checks whether the 'timestamp' field has been set. + * @return True if the 'timestamp' field has been set, false otherwise. + */ public boolean hasTimestamp() { return fieldSetFlags()[0]; } - - /** Clears the value of the 'timestamp' field */ + + + /** + * Clears the value of the 'timestamp' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTimestamp() { timestamp = null; fieldSetFlags()[0] = false; return this; } - /** Gets the value of the 'service' field */ + /** + * Gets the value of the 'service' field. + * @return The value. + */ public java.lang.String getService() { return service; } - - /** Sets the value of the 'service' field */ + + /** + * Sets the value of the 'service' field. + * @param value The value of 'service'. + * @return This builder. + */ public argo.avro.MetricData.Builder setService(java.lang.String value) { validate(fields()[1], value); this.service = value; fieldSetFlags()[1] = true; - return this; + return this; } - - /** Checks whether the 'service' field has been set */ + + /** + * Checks whether the 'service' field has been set. + * @return True if the 'service' field has been set, false otherwise. + */ public boolean hasService() { return fieldSetFlags()[1]; } - - /** Clears the value of the 'service' field */ + + + /** + * Clears the value of the 'service' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearService() { service = null; fieldSetFlags()[1] = false; return this; } - /** Gets the value of the 'hostname' field */ + /** + * Gets the value of the 'hostname' field. + * @return The value. + */ public java.lang.String getHostname() { return hostname; } - - /** Sets the value of the 'hostname' field */ + + /** + * Sets the value of the 'hostname' field. + * @param value The value of 'hostname'. + * @return This builder. + */ public argo.avro.MetricData.Builder setHostname(java.lang.String value) { validate(fields()[2], value); this.hostname = value; fieldSetFlags()[2] = true; - return this; + return this; } - - /** Checks whether the 'hostname' field has been set */ + + /** + * Checks whether the 'hostname' field has been set. + * @return True if the 'hostname' field has been set, false otherwise. + */ public boolean hasHostname() { return fieldSetFlags()[2]; } - - /** Clears the value of the 'hostname' field */ + + + /** + * Clears the value of the 'hostname' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearHostname() { hostname = null; fieldSetFlags()[2] = false; return this; } - /** Gets the value of the 'metric' field */ + /** + * Gets the value of the 'metric' field. + * @return The value. + */ public java.lang.String getMetric() { return metric; } - - /** Sets the value of the 'metric' field */ + + /** + * Sets the value of the 'metric' field. + * @param value The value of 'metric'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMetric(java.lang.String value) { validate(fields()[3], value); this.metric = value; fieldSetFlags()[3] = true; - return this; + return this; } - - /** Checks whether the 'metric' field has been set */ + + /** + * Checks whether the 'metric' field has been set. + * @return True if the 'metric' field has been set, false otherwise. + */ public boolean hasMetric() { return fieldSetFlags()[3]; } - - /** Clears the value of the 'metric' field */ + + + /** + * Clears the value of the 'metric' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMetric() { metric = null; fieldSetFlags()[3] = false; return this; } - /** Gets the value of the 'status' field */ + /** + * Gets the value of the 'status' field. + * @return The value. + */ public java.lang.String getStatus() { return status; } - - /** Sets the value of the 'status' field */ + + /** + * Sets the value of the 'status' field. + * @param value The value of 'status'. + * @return This builder. + */ public argo.avro.MetricData.Builder setStatus(java.lang.String value) { validate(fields()[4], value); this.status = value; fieldSetFlags()[4] = true; - return this; + return this; } - - /** Checks whether the 'status' field has been set */ + + /** + * Checks whether the 'status' field has been set. + * @return True if the 'status' field has been set, false otherwise. + */ public boolean hasStatus() { return fieldSetFlags()[4]; } - - /** Clears the value of the 'status' field */ + + + /** + * Clears the value of the 'status' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearStatus() { status = null; fieldSetFlags()[4] = false; return this; } - /** Gets the value of the 'monitoring_host' field */ + /** + * Gets the value of the 'monitoring_host' field. + * @return The value. + */ public java.lang.String getMonitoringHost() { return monitoring_host; } - - /** Sets the value of the 'monitoring_host' field */ + + /** + * Sets the value of the 'monitoring_host' field. + * @param value The value of 'monitoring_host'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMonitoringHost(java.lang.String value) { validate(fields()[5], value); this.monitoring_host = value; fieldSetFlags()[5] = true; - return this; + return this; } - - /** Checks whether the 'monitoring_host' field has been set */ + + /** + * Checks whether the 'monitoring_host' field has been set. + * @return True if the 'monitoring_host' field has been set, false otherwise. + */ public boolean hasMonitoringHost() { return fieldSetFlags()[5]; } - - /** Clears the value of the 'monitoring_host' field */ + + + /** + * Clears the value of the 'monitoring_host' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMonitoringHost() { monitoring_host = null; fieldSetFlags()[5] = false; return this; } - /** Gets the value of the 'summary' field */ + /** + * Gets the value of the 'actual_data' field. + * @return The value. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value The value of 'actual_data'. + * @return This builder. + */ + public argo.avro.MetricData.Builder setActualData(java.lang.String value) { + validate(fields()[6], value); + this.actual_data = value; + fieldSetFlags()[6] = true; + return this; + } + + /** + * Checks whether the 'actual_data' field has been set. + * @return True if the 'actual_data' field has been set, false otherwise. + */ + public boolean hasActualData() { + return fieldSetFlags()[6]; + } + + + /** + * Clears the value of the 'actual_data' field. + * @return This builder. + */ + public argo.avro.MetricData.Builder clearActualData() { + actual_data = null; + fieldSetFlags()[6] = false; + return this; + } + + /** + * Gets the value of the 'summary' field. + * @return The value. + */ public java.lang.String getSummary() { return summary; } - - /** Sets the value of the 'summary' field */ + + /** + * Sets the value of the 'summary' field. + * @param value The value of 'summary'. + * @return This builder. + */ public argo.avro.MetricData.Builder setSummary(java.lang.String value) { - validate(fields()[6], value); + validate(fields()[7], value); this.summary = value; - fieldSetFlags()[6] = true; - return this; + fieldSetFlags()[7] = true; + return this; } - - /** Checks whether the 'summary' field has been set */ + + /** + * Checks whether the 'summary' field has been set. + * @return True if the 'summary' field has been set, false otherwise. + */ public boolean hasSummary() { - return fieldSetFlags()[6]; + return fieldSetFlags()[7]; } - - /** Clears the value of the 'summary' field */ + + + /** + * Clears the value of the 'summary' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearSummary() { summary = null; - fieldSetFlags()[6] = false; + fieldSetFlags()[7] = false; return this; } - /** Gets the value of the 'message' field */ + /** + * Gets the value of the 'message' field. + * @return The value. + */ public java.lang.String getMessage() { return message; } - - /** Sets the value of the 'message' field */ + + /** + * Sets the value of the 'message' field. + * @param value The value of 'message'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMessage(java.lang.String value) { - validate(fields()[7], value); + validate(fields()[8], value); this.message = value; - fieldSetFlags()[7] = true; - return this; + fieldSetFlags()[8] = true; + return this; } - - /** Checks whether the 'message' field has been set */ + + /** + * Checks whether the 'message' field has been set. + * @return True if the 'message' field has been set, false otherwise. + */ public boolean hasMessage() { - return fieldSetFlags()[7]; + return fieldSetFlags()[8]; } - - /** Clears the value of the 'message' field */ + + + /** + * Clears the value of the 'message' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMessage() { message = null; - fieldSetFlags()[7] = false; + fieldSetFlags()[8] = false; return this; } - /** Gets the value of the 'tags' field */ + /** + * Gets the value of the 'tags' field. + * @return The value. + */ public java.util.Map getTags() { return tags; } - - /** Sets the value of the 'tags' field */ + + /** + * Sets the value of the 'tags' field. + * @param value The value of 'tags'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTags(java.util.Map value) { - validate(fields()[8], value); + validate(fields()[9], value); this.tags = value; - fieldSetFlags()[8] = true; - return this; + fieldSetFlags()[9] = true; + return this; } - - /** Checks whether the 'tags' field has been set */ + + /** + * Checks whether the 'tags' field has been set. + * @return True if the 'tags' field has been set, false otherwise. + */ public boolean hasTags() { - return fieldSetFlags()[8]; + return fieldSetFlags()[9]; } - - /** Clears the value of the 'tags' field */ + + + /** + * Clears the value of the 'tags' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTags() { tags = null; - fieldSetFlags()[8] = false; + fieldSetFlags()[9] = false; return this; } @@ -524,13 +797,15 @@ public MetricData build() { record.metric = fieldSetFlags()[3] ? this.metric : (java.lang.String) defaultValue(fields()[3]); record.status = fieldSetFlags()[4] ? this.status : (java.lang.String) defaultValue(fields()[4]); record.monitoring_host = fieldSetFlags()[5] ? this.monitoring_host : (java.lang.String) defaultValue(fields()[5]); - record.summary = fieldSetFlags()[6] ? this.summary : (java.lang.String) defaultValue(fields()[6]); - record.message = fieldSetFlags()[7] ? this.message : (java.lang.String) defaultValue(fields()[7]); - record.tags = fieldSetFlags()[8] ? this.tags : (java.util.Map) defaultValue(fields()[8]); + record.actual_data = fieldSetFlags()[6] ? this.actual_data : (java.lang.String) defaultValue(fields()[6]); + record.summary = fieldSetFlags()[7] ? this.summary : (java.lang.String) defaultValue(fields()[7]); + record.message = fieldSetFlags()[8] ? this.message : (java.lang.String) defaultValue(fields()[8]); + record.tags = fieldSetFlags()[9] ? this.tags : (java.util.Map) defaultValue(fields()[9]); return record; } catch (Exception e) { throw new org.apache.avro.AvroRuntimeException(e); } } } + } diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricDataOld.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricDataOld.java new file mode 100644 index 00000000..dafb8d40 --- /dev/null +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/avro/MetricDataOld.java @@ -0,0 +1,536 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package argo.avro; +@SuppressWarnings("all") +@org.apache.avro.specific.AvroGenerated +public class MetricDataOld extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); + public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } + @Deprecated public java.lang.String timestamp; + @Deprecated public java.lang.String service; + @Deprecated public java.lang.String hostname; + @Deprecated public java.lang.String metric; + @Deprecated public java.lang.String status; + @Deprecated public java.lang.String monitoring_host; + @Deprecated public java.lang.String summary; + @Deprecated public java.lang.String message; + @Deprecated public java.util.Map tags; + + /** + * Default constructor. + */ + public MetricDataOld() {} + + /** + * All-args constructor. + */ + public MetricDataOld(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String summary, java.lang.String message, java.util.Map tags) { + this.timestamp = timestamp; + this.service = service; + this.hostname = hostname; + this.metric = metric; + this.status = status; + this.monitoring_host = monitoring_host; + this.summary = summary; + this.message = message; + this.tags = tags; + } + + public org.apache.avro.Schema getSchema() { return SCHEMA$; } + // Used by DatumWriter. Applications should not call. + public java.lang.Object get(int field$) { + switch (field$) { + case 0: return timestamp; + case 1: return service; + case 2: return hostname; + case 3: return metric; + case 4: return status; + case 5: return monitoring_host; + case 6: return summary; + case 7: return message; + case 8: return tags; + default: throw new org.apache.avro.AvroRuntimeException("Bad index"); + } + } + // Used by DatumReader. Applications should not call. + @SuppressWarnings(value="unchecked") + public void put(int field$, java.lang.Object value$) { + switch (field$) { + case 0: timestamp = (java.lang.String)value$; break; + case 1: service = (java.lang.String)value$; break; + case 2: hostname = (java.lang.String)value$; break; + case 3: metric = (java.lang.String)value$; break; + case 4: status = (java.lang.String)value$; break; + case 5: monitoring_host = (java.lang.String)value$; break; + case 6: summary = (java.lang.String)value$; break; + case 7: message = (java.lang.String)value$; break; + case 8: tags = (java.util.Map)value$; break; + default: throw new org.apache.avro.AvroRuntimeException("Bad index"); + } + } + + /** + * Gets the value of the 'timestamp' field. + */ + public java.lang.String getTimestamp() { + return timestamp; + } + + /** + * Sets the value of the 'timestamp' field. + * @param value the value to set. + */ + public void setTimestamp(java.lang.String value) { + this.timestamp = value; + } + + /** + * Gets the value of the 'service' field. + */ + public java.lang.String getService() { + return service; + } + + /** + * Sets the value of the 'service' field. + * @param value the value to set. + */ + public void setService(java.lang.String value) { + this.service = value; + } + + /** + * Gets the value of the 'hostname' field. + */ + public java.lang.String getHostname() { + return hostname; + } + + /** + * Sets the value of the 'hostname' field. + * @param value the value to set. + */ + public void setHostname(java.lang.String value) { + this.hostname = value; + } + + /** + * Gets the value of the 'metric' field. + */ + public java.lang.String getMetric() { + return metric; + } + + /** + * Sets the value of the 'metric' field. + * @param value the value to set. + */ + public void setMetric(java.lang.String value) { + this.metric = value; + } + + /** + * Gets the value of the 'status' field. + */ + public java.lang.String getStatus() { + return status; + } + + /** + * Sets the value of the 'status' field. + * @param value the value to set. + */ + public void setStatus(java.lang.String value) { + this.status = value; + } + + /** + * Gets the value of the 'monitoring_host' field. + */ + public java.lang.String getMonitoringHost() { + return monitoring_host; + } + + /** + * Sets the value of the 'monitoring_host' field. + * @param value the value to set. + */ + public void setMonitoringHost(java.lang.String value) { + this.monitoring_host = value; + } + + /** + * Gets the value of the 'summary' field. + */ + public java.lang.String getSummary() { + return summary; + } + + /** + * Sets the value of the 'summary' field. + * @param value the value to set. + */ + public void setSummary(java.lang.String value) { + this.summary = value; + } + + /** + * Gets the value of the 'message' field. + */ + public java.lang.String getMessage() { + return message; + } + + /** + * Sets the value of the 'message' field. + * @param value the value to set. + */ + public void setMessage(java.lang.String value) { + this.message = value; + } + + /** + * Gets the value of the 'tags' field. + */ + public java.util.Map getTags() { + return tags; + } + + /** + * Sets the value of the 'tags' field. + * @param value the value to set. + */ + public void setTags(java.util.Map value) { + this.tags = value; + } + + /** Creates a new MetricData RecordBuilder */ + public static argo.avro.MetricDataOld.Builder newBuilder() { + return new argo.avro.MetricDataOld.Builder(); + } + + /** Creates a new MetricData RecordBuilder by copying an existing Builder */ + public static argo.avro.MetricDataOld.Builder newBuilder(argo.avro.MetricDataOld.Builder other) { + return new argo.avro.MetricDataOld.Builder(other); + } + + /** Creates a new MetricData RecordBuilder by copying an existing MetricData instance */ + public static argo.avro.MetricDataOld.Builder newBuilder(argo.avro.MetricDataOld other) { + return new argo.avro.MetricDataOld.Builder(other); + } + + /** + * RecordBuilder for MetricData instances. + */ + public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase + implements org.apache.avro.data.RecordBuilder { + + private java.lang.String timestamp; + private java.lang.String service; + private java.lang.String hostname; + private java.lang.String metric; + private java.lang.String status; + private java.lang.String monitoring_host; + private java.lang.String summary; + private java.lang.String message; + private java.util.Map tags; + + /** Creates a new Builder */ + private Builder() { + super(argo.avro.MetricDataOld.SCHEMA$); + } + + /** Creates a Builder by copying an existing Builder */ + private Builder(argo.avro.MetricDataOld.Builder other) { + super(other); + } + + /** Creates a Builder by copying an existing MetricData instance */ + private Builder(argo.avro.MetricDataOld other) { + super(argo.avro.MetricDataOld.SCHEMA$); + if (isValidValue(fields()[0], other.timestamp)) { + this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.service)) { + this.service = data().deepCopy(fields()[1].schema(), other.service); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.hostname)) { + this.hostname = data().deepCopy(fields()[2].schema(), other.hostname); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.metric)) { + this.metric = data().deepCopy(fields()[3].schema(), other.metric); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.status)) { + this.status = data().deepCopy(fields()[4].schema(), other.status); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.monitoring_host)) { + this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); + fieldSetFlags()[5] = true; + } + if (isValidValue(fields()[6], other.summary)) { + this.summary = data().deepCopy(fields()[6].schema(), other.summary); + fieldSetFlags()[6] = true; + } + if (isValidValue(fields()[7], other.message)) { + this.message = data().deepCopy(fields()[7].schema(), other.message); + fieldSetFlags()[7] = true; + } + if (isValidValue(fields()[8], other.tags)) { + this.tags = data().deepCopy(fields()[8].schema(), other.tags); + fieldSetFlags()[8] = true; + } + } + + /** Gets the value of the 'timestamp' field */ + public java.lang.String getTimestamp() { + return timestamp; + } + + /** Sets the value of the 'timestamp' field */ + public argo.avro.MetricDataOld.Builder setTimestamp(java.lang.String value) { + validate(fields()[0], value); + this.timestamp = value; + fieldSetFlags()[0] = true; + return this; + } + + /** Checks whether the 'timestamp' field has been set */ + public boolean hasTimestamp() { + return fieldSetFlags()[0]; + } + + /** Clears the value of the 'timestamp' field */ + public argo.avro.MetricDataOld.Builder clearTimestamp() { + timestamp = null; + fieldSetFlags()[0] = false; + return this; + } + + /** Gets the value of the 'service' field */ + public java.lang.String getService() { + return service; + } + + /** Sets the value of the 'service' field */ + public argo.avro.MetricDataOld.Builder setService(java.lang.String value) { + validate(fields()[1], value); + this.service = value; + fieldSetFlags()[1] = true; + return this; + } + + /** Checks whether the 'service' field has been set */ + public boolean hasService() { + return fieldSetFlags()[1]; + } + + /** Clears the value of the 'service' field */ + public argo.avro.MetricDataOld.Builder clearService() { + service = null; + fieldSetFlags()[1] = false; + return this; + } + + /** Gets the value of the 'hostname' field */ + public java.lang.String getHostname() { + return hostname; + } + + /** Sets the value of the 'hostname' field */ + public argo.avro.MetricDataOld.Builder setHostname(java.lang.String value) { + validate(fields()[2], value); + this.hostname = value; + fieldSetFlags()[2] = true; + return this; + } + + /** Checks whether the 'hostname' field has been set */ + public boolean hasHostname() { + return fieldSetFlags()[2]; + } + + /** Clears the value of the 'hostname' field */ + public argo.avro.MetricDataOld.Builder clearHostname() { + hostname = null; + fieldSetFlags()[2] = false; + return this; + } + + /** Gets the value of the 'metric' field */ + public java.lang.String getMetric() { + return metric; + } + + /** Sets the value of the 'metric' field */ + public argo.avro.MetricDataOld.Builder setMetric(java.lang.String value) { + validate(fields()[3], value); + this.metric = value; + fieldSetFlags()[3] = true; + return this; + } + + /** Checks whether the 'metric' field has been set */ + public boolean hasMetric() { + return fieldSetFlags()[3]; + } + + /** Clears the value of the 'metric' field */ + public argo.avro.MetricDataOld.Builder clearMetric() { + metric = null; + fieldSetFlags()[3] = false; + return this; + } + + /** Gets the value of the 'status' field */ + public java.lang.String getStatus() { + return status; + } + + /** Sets the value of the 'status' field */ + public argo.avro.MetricDataOld.Builder setStatus(java.lang.String value) { + validate(fields()[4], value); + this.status = value; + fieldSetFlags()[4] = true; + return this; + } + + /** Checks whether the 'status' field has been set */ + public boolean hasStatus() { + return fieldSetFlags()[4]; + } + + /** Clears the value of the 'status' field */ + public argo.avro.MetricDataOld.Builder clearStatus() { + status = null; + fieldSetFlags()[4] = false; + return this; + } + + /** Gets the value of the 'monitoring_host' field */ + public java.lang.String getMonitoringHost() { + return monitoring_host; + } + + /** Sets the value of the 'monitoring_host' field */ + public argo.avro.MetricDataOld.Builder setMonitoringHost(java.lang.String value) { + validate(fields()[5], value); + this.monitoring_host = value; + fieldSetFlags()[5] = true; + return this; + } + + /** Checks whether the 'monitoring_host' field has been set */ + public boolean hasMonitoringHost() { + return fieldSetFlags()[5]; + } + + /** Clears the value of the 'monitoring_host' field */ + public argo.avro.MetricDataOld.Builder clearMonitoringHost() { + monitoring_host = null; + fieldSetFlags()[5] = false; + return this; + } + + /** Gets the value of the 'summary' field */ + public java.lang.String getSummary() { + return summary; + } + + /** Sets the value of the 'summary' field */ + public argo.avro.MetricDataOld.Builder setSummary(java.lang.String value) { + validate(fields()[6], value); + this.summary = value; + fieldSetFlags()[6] = true; + return this; + } + + /** Checks whether the 'summary' field has been set */ + public boolean hasSummary() { + return fieldSetFlags()[6]; + } + + /** Clears the value of the 'summary' field */ + public argo.avro.MetricDataOld.Builder clearSummary() { + summary = null; + fieldSetFlags()[6] = false; + return this; + } + + /** Gets the value of the 'message' field */ + public java.lang.String getMessage() { + return message; + } + + /** Sets the value of the 'message' field */ + public argo.avro.MetricDataOld.Builder setMessage(java.lang.String value) { + validate(fields()[7], value); + this.message = value; + fieldSetFlags()[7] = true; + return this; + } + + /** Checks whether the 'message' field has been set */ + public boolean hasMessage() { + return fieldSetFlags()[7]; + } + + /** Clears the value of the 'message' field */ + public argo.avro.MetricDataOld.Builder clearMessage() { + message = null; + fieldSetFlags()[7] = false; + return this; + } + + /** Gets the value of the 'tags' field */ + public java.util.Map getTags() { + return tags; + } + + /** Sets the value of the 'tags' field */ + public argo.avro.MetricDataOld.Builder setTags(java.util.Map value) { + validate(fields()[8], value); + this.tags = value; + fieldSetFlags()[8] = true; + return this; + } + + /** Checks whether the 'tags' field has been set */ + public boolean hasTags() { + return fieldSetFlags()[8]; + } + + /** Clears the value of the 'tags' field */ + public argo.avro.MetricDataOld.Builder clearTags() { + tags = null; + fieldSetFlags()[8] = false; + return this; + } + + @Override + public MetricDataOld build() { + try { + MetricDataOld record = new MetricDataOld(); + record.timestamp = fieldSetFlags()[0] ? this.timestamp : (java.lang.String) defaultValue(fields()[0]); + record.service = fieldSetFlags()[1] ? this.service : (java.lang.String) defaultValue(fields()[1]); + record.hostname = fieldSetFlags()[2] ? this.hostname : (java.lang.String) defaultValue(fields()[2]); + record.metric = fieldSetFlags()[3] ? this.metric : (java.lang.String) defaultValue(fields()[3]); + record.status = fieldSetFlags()[4] ? this.status : (java.lang.String) defaultValue(fields()[4]); + record.monitoring_host = fieldSetFlags()[5] ? this.monitoring_host : (java.lang.String) defaultValue(fields()[5]); + record.summary = fieldSetFlags()[6] ? this.summary : (java.lang.String) defaultValue(fields()[6]); + record.message = fieldSetFlags()[7] ? this.message : (java.lang.String) defaultValue(fields()[7]); + record.tags = fieldSetFlags()[8] ? this.tags : (java.util.Map) defaultValue(fields()[8]); + return record; + } catch (Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + } +} diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/AmsIngestMetric.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/AmsIngestMetric.java index 7b8c10ac..78a0fac3 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/AmsIngestMetric.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/AmsIngestMetric.java @@ -1,6 +1,10 @@ package argo.streaming; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.apache.avro.AvroRuntimeException; import org.apache.avro.io.DatumReader; import org.apache.avro.io.Decoder; import org.apache.avro.io.DecoderFactory; @@ -8,7 +12,9 @@ import org.apache.avro.specific.SpecificDatumReader; import org.apache.commons.codec.binary.Base64; import org.apache.flink.api.common.functions.FlatMapFunction; - +import org.apache.flink.api.common.io.OutputFormat; +import org.apache.flink.api.common.restartstrategy.RestartStrategies; +import org.apache.flink.api.common.time.Time; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.runtime.state.filesystem.FsStateBackend; @@ -30,6 +36,7 @@ import com.google.gson.JsonParser; import argo.avro.MetricData; +import argo.avro.MetricDataOld; /** @@ -111,6 +118,8 @@ public static void main(String[] args) throws Exception { // Create flink execution environment StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); see.setParallelism(1); + // On failure attempt max 10 times to restart with a retry interval of 2 minutes + see.setRestartStrategy(RestartStrategies.fixedDelayRestart(10, Time.of(2, TimeUnit.MINUTES))); // Initialize cli parameter tool final ParameterTool parameterTool = ParameterTool.fromArgs(args); @@ -176,14 +185,26 @@ public void flatMap(String value, Collector out) throws Exception { // Decode from base64 byte[] decoded64 = Base64.decodeBase64(data.getBytes("UTF-8")); // Decode from avro - DatumReader avroReader = new SpecificDatumReader(MetricData.getClassSchema(), - MetricData.getClassSchema(), new SpecificData()); + + DatumReader avroReader = new SpecificDatumReader(MetricData.getClassSchema()); Decoder decoder = DecoderFactory.get().binaryDecoder(decoded64, null); + + MetricData item; + try { item = avroReader.read(null, decoder); + } catch (java.io.EOFException ex) + { + //convert from old to new + avroReader = new SpecificDatumReader(MetricDataOld.getClassSchema(),MetricData.getClassSchema()); + decoder = DecoderFactory.get().binaryDecoder(decoded64, null); + item = avroReader.read(null, decoder); + } if (item != null) { + LOG.info("Captured data -- {}", item.toString()); out.collect(item); - } + } + } }); diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/ArgoMessagingClient.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/ArgoMessagingClient.java index 81af370c..76305f62 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/ArgoMessagingClient.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/ArgoMessagingClient.java @@ -26,13 +26,15 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; /** - * Simple http client for pulling and acknowledging messages from AMS service http API + * Simple http client for pulling and acknowledging messages from AMS service + * http API */ public class ArgoMessagingClient { @@ -53,9 +55,9 @@ public class ArgoMessagingClient { private String maxMessages = ""; // ssl verify or not private boolean verify = true; - // proxy + // proxy private URI proxy = null; - + // Utility inner class for holding list of messages and acknowledgements private class MsgAck { String[] msgs; @@ -78,8 +80,9 @@ public ArgoMessagingClient() { this.maxMessages = "100"; this.proxy = null; } - - public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + + public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, + boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { this.proto = method; this.token = token; @@ -88,33 +91,36 @@ public ArgoMessagingClient(String method, String token, String endpoint, String this.sub = sub; this.maxMessages = String.valueOf(batch); this.verify = verify; - + this.httpClient = buildHttpClient(); - + } - + /** * Initializes Http Client (if not initialized during constructor) - * @return + * + * @return */ - private CloseableHttpClient buildHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private CloseableHttpClient buildHttpClient() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (this.verify) { return this.httpClient = HttpClients.createDefault(); } else { return this.httpClient = HttpClients.custom().setSSLSocketFactory(selfSignedSSLF()).build(); } } - + /** - * Create an SSL Connection Socket Factory with a strategy to trust self signed certificates + * Create an SSL Connection Socket Factory with a strategy to trust self signed + * certificates */ - private SSLConnectionSocketFactory selfSignedSSLF() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private SSLConnectionSocketFactory selfSignedSSLF() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContextBuilder sslBuild = new SSLContextBuilder(); - sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); - return new SSLConnectionSocketFactory(sslBuild.build(),NoopHostnameVerifier.INSTANCE); + sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); + return new SSLConnectionSocketFactory(sslBuild.build(), NoopHostnameVerifier.INSTANCE); } - - + /** * Set AMS http client to use http proxy */ @@ -122,26 +128,37 @@ public void setProxy(String proxyURL) throws URISyntaxException { // parse proxy url this.proxy = URI.create(proxyURL); } - + /** * Set AMS http client to NOT use an http proxy */ public void unsetProxy() { - this.proxy=null; + this.proxy = null; } - - /** * Create a configuration for using http proxy on each request */ private RequestConfig createProxyCfg() { - HttpHost proxy = new HttpHost(this.proxy.getHost(),this.proxy.getPort(),this.proxy.getScheme()); + HttpHost proxy = new HttpHost(this.proxy.getHost(), this.proxy.getPort(), this.proxy.getScheme()); RequestConfig config = RequestConfig.custom().setProxy(proxy).build(); return config; } + public void logIssue(CloseableHttpResponse resp) throws UnsupportedOperationException, IOException { + InputStreamReader isRdr = new InputStreamReader(resp.getEntity().getContent()); + BufferedReader bRdr = new BufferedReader(isRdr); + int statusCode = resp.getStatusLine().getStatusCode(); + // Parse error content from api response + StringBuilder result = new StringBuilder(); + String rLine; + while ((rLine = bRdr.readLine()) != null) + result.append(rLine); + isRdr.close(); + Log.warn("ApiStatusCode={}, ApiErrorMessage={}", statusCode, result); + + } /** * Properly compose url for each AMS request @@ -170,11 +187,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit this.httpClient = buildHttpClient(); } - // check for proxy + // check for proxy if (this.proxy != null) { postPull.setConfig(createProxyCfg()); } - + CloseableHttpResponse response = this.httpClient.execute(postPull); String msg = ""; String ackId = ""; @@ -182,7 +199,9 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit HttpEntity entity = response.getEntity(); - if (entity != null) { + int statusCode = response.getStatusLine().getStatusCode(); + + if (entity != null && statusCode == 200) { InputStreamReader isRdr = new InputStreamReader(entity.getContent()); BufferedReader bRdr = new BufferedReader(isRdr); @@ -195,13 +214,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit // Gather message from json JsonParser jsonParser = new JsonParser(); // parse the json root object - + Log.info("response: {}", result.toString()); JsonElement jRoot = jsonParser.parse(result.toString()); JsonArray jRec = jRoot.getAsJsonObject().get("receivedMessages").getAsJsonArray(); - - // if has elements for (JsonElement jMsgItem : jRec) { JsonElement jMsg = jMsgItem.getAsJsonObject().get("message"); @@ -211,9 +228,13 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit msgList.add(msg); ackIdList.add(ackId); } - + isRdr.close(); + } else { + + logIssue(response); + } response.close(); @@ -221,8 +242,6 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit String[] msgArr = msgList.toArray(new String[0]); String[] ackIdArr = ackIdList.toArray(new String[0]); - - // Return a Message array return new MsgAck(msgArr, ackIdArr); @@ -257,7 +276,6 @@ public String[] consume() throws KeyManagementException, NoSuchAlgorithmExceptio } catch (IOException e) { LOG.error(e.getMessage()); } - return msgs; } @@ -272,8 +290,8 @@ public String doAck(String ackId) throws IOException { StringEntity postBody = new StringEntity("{\"ackIds\":[" + ackId + "]}"); postBody.setContentType("application/json"); postAck.setEntity(postBody); - - // check for proxy + + // check for proxy if (this.proxy != null) { postAck.setConfig(createProxyCfg()); } @@ -298,10 +316,11 @@ public String doAck(String ackId) throws IOException { resMsg = result.toString(); isRdr.close(); + } else { + // Log any api errors + logIssue(response); } - response.close(); - // Return a resposeMessage return resMsg; diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/HBaseMetricOutputFormat.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/HBaseMetricOutputFormat.java index 0e62c5ed..5a4b084e 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/HBaseMetricOutputFormat.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/HBaseMetricOutputFormat.java @@ -13,6 +13,7 @@ import org.apache.hadoop.hbase.util.Bytes; import argo.avro.MetricData; +import argo.avro.MetricDataOld; /** * Hbase Output Format for storing Metric Data to an hbase destination diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/MetricParse.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/MetricParse.java index 8b49fa71..40873bfb 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/MetricParse.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/MetricParse.java @@ -16,6 +16,7 @@ import com.google.gson.JsonElement; import argo.avro.MetricData; +import argo.avro.MetricDataOld; @@ -31,7 +32,7 @@ public static ArrayList parseGroupEndpoint(byte[] avroBytes) throws ArrayList result = new ArrayList(); - DatumReader avroReader = new SpecificDatumReader(MetricData.getClassSchema(),MetricData.getClassSchema(),new SpecificData()); + DatumReader avroReader = new SpecificDatumReader(MetricDataOld.getClassSchema(),MetricData.getClassSchema(),new SpecificData()); BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(avroBytes, null); while (!decoder.isEnd()){ diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/SpecificAvroWriter.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/SpecificAvroWriter.java index 72da6de8..5ea8c472 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/SpecificAvroWriter.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/SpecificAvroWriter.java @@ -86,7 +86,6 @@ public long flush() throws IOException { @Override public Writer duplicate() { - // TODO Auto-generated method stub return new SpecificAvroWriter(); } diff --git a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/TSBucketer.java b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/TSBucketer.java index 56e87c0c..bae5cedf 100644 --- a/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/TSBucketer.java +++ b/flink_jobs/ams_ingest_metric/src/main/java/argo/streaming/TSBucketer.java @@ -4,6 +4,8 @@ import org.apache.flink.streaming.connectors.fs.bucketing.Bucketer; import org.apache.hadoop.fs.Path; +import com.esotericsoftware.minlog.Log; + import argo.avro.MetricData; @@ -18,7 +20,6 @@ public class TSBucketer implements Bucketer { public Path getBucketPath(final Clock clock, final Path basePath, final MetricData element) { String dailyPart = element.getTimestamp().split("T")[0]; - return new Path(basePath + "/" + dailyPart); } } \ No newline at end of file diff --git a/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/AmsIngestSync.java b/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/AmsIngestSync.java index 77ac538e..43815d66 100644 --- a/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/AmsIngestSync.java +++ b/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/AmsIngestSync.java @@ -1,5 +1,9 @@ package argo.streaming; +import java.util.concurrent.TimeUnit; + +import org.apache.flink.api.common.restartstrategy.RestartStrategies; +import org.apache.flink.api.common.time.Time; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; @@ -53,6 +57,8 @@ public static void main(String[] args) throws Exception { // Create flink execution enviroment StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); see.setParallelism(1); + // Fixed restart strategy: on failure attempt max 10 times to restart with a retry interval of 2 minutes + see.setRestartStrategy(RestartStrategies.fixedDelayRestart(10, Time.of(2, TimeUnit.MINUTES))); // Initialize cli parameter tool final ParameterTool parameterTool = ParameterTool.fromArgs(args); diff --git a/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/ArgoMessagingClient.java b/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/ArgoMessagingClient.java index 81af370c..4e6e1527 100644 --- a/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/ArgoMessagingClient.java +++ b/flink_jobs/ams_ingest_sync/src/main/java/argo/streaming/ArgoMessagingClient.java @@ -32,7 +32,8 @@ import org.apache.http.client.methods.CloseableHttpResponse; /** - * Simple http client for pulling and acknowledging messages from AMS service http API + * Simple http client for pulling and acknowledging messages from AMS service + * http API */ public class ArgoMessagingClient { @@ -53,9 +54,9 @@ public class ArgoMessagingClient { private String maxMessages = ""; // ssl verify or not private boolean verify = true; - // proxy + // proxy private URI proxy = null; - + // Utility inner class for holding list of messages and acknowledgements private class MsgAck { String[] msgs; @@ -78,8 +79,9 @@ public ArgoMessagingClient() { this.maxMessages = "100"; this.proxy = null; } - - public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + + public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, + boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { this.proto = method; this.token = token; @@ -88,33 +90,36 @@ public ArgoMessagingClient(String method, String token, String endpoint, String this.sub = sub; this.maxMessages = String.valueOf(batch); this.verify = verify; - + this.httpClient = buildHttpClient(); - + } - + /** * Initializes Http Client (if not initialized during constructor) - * @return + * + * @return */ - private CloseableHttpClient buildHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private CloseableHttpClient buildHttpClient() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (this.verify) { return this.httpClient = HttpClients.createDefault(); } else { return this.httpClient = HttpClients.custom().setSSLSocketFactory(selfSignedSSLF()).build(); } } - + /** - * Create an SSL Connection Socket Factory with a strategy to trust self signed certificates + * Create an SSL Connection Socket Factory with a strategy to trust self signed + * certificates */ - private SSLConnectionSocketFactory selfSignedSSLF() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private SSLConnectionSocketFactory selfSignedSSLF() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContextBuilder sslBuild = new SSLContextBuilder(); - sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); - return new SSLConnectionSocketFactory(sslBuild.build(),NoopHostnameVerifier.INSTANCE); + sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); + return new SSLConnectionSocketFactory(sslBuild.build(), NoopHostnameVerifier.INSTANCE); } - - + /** * Set AMS http client to use http proxy */ @@ -122,26 +127,37 @@ public void setProxy(String proxyURL) throws URISyntaxException { // parse proxy url this.proxy = URI.create(proxyURL); } - + /** * Set AMS http client to NOT use an http proxy */ public void unsetProxy() { - this.proxy=null; + this.proxy = null; } - - /** * Create a configuration for using http proxy on each request */ private RequestConfig createProxyCfg() { - HttpHost proxy = new HttpHost(this.proxy.getHost(),this.proxy.getPort(),this.proxy.getScheme()); + HttpHost proxy = new HttpHost(this.proxy.getHost(), this.proxy.getPort(), this.proxy.getScheme()); RequestConfig config = RequestConfig.custom().setProxy(proxy).build(); return config; } + public void logIssue(CloseableHttpResponse resp) throws UnsupportedOperationException, IOException { + InputStreamReader isRdr = new InputStreamReader(resp.getEntity().getContent()); + BufferedReader bRdr = new BufferedReader(isRdr); + int statusCode = resp.getStatusLine().getStatusCode(); + // Parse error content from api response + StringBuilder result = new StringBuilder(); + String rLine; + while ((rLine = bRdr.readLine()) != null) + result.append(rLine); + isRdr.close(); + Log.warn("ApiStatusCode={}, ApiErrorMessage={}", statusCode, result); + + } /** * Properly compose url for each AMS request @@ -170,11 +186,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit this.httpClient = buildHttpClient(); } - // check for proxy + // check for proxy if (this.proxy != null) { postPull.setConfig(createProxyCfg()); } - + CloseableHttpResponse response = this.httpClient.execute(postPull); String msg = ""; String ackId = ""; @@ -182,7 +198,9 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit HttpEntity entity = response.getEntity(); - if (entity != null) { + int statusCode = response.getStatusLine().getStatusCode(); + + if (entity != null && statusCode == 200) { InputStreamReader isRdr = new InputStreamReader(entity.getContent()); BufferedReader bRdr = new BufferedReader(isRdr); @@ -195,13 +213,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit // Gather message from json JsonParser jsonParser = new JsonParser(); // parse the json root object - + Log.info("response: {}", result.toString()); JsonElement jRoot = jsonParser.parse(result.toString()); JsonArray jRec = jRoot.getAsJsonObject().get("receivedMessages").getAsJsonArray(); - - // if has elements for (JsonElement jMsgItem : jRec) { JsonElement jMsg = jMsgItem.getAsJsonObject().get("message"); @@ -211,9 +227,13 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit msgList.add(msg); ackIdList.add(ackId); } - + isRdr.close(); + } else { + + logIssue(response); + } response.close(); @@ -221,8 +241,6 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit String[] msgArr = msgList.toArray(new String[0]); String[] ackIdArr = ackIdList.toArray(new String[0]); - - // Return a Message array return new MsgAck(msgArr, ackIdArr); @@ -257,7 +275,6 @@ public String[] consume() throws KeyManagementException, NoSuchAlgorithmExceptio } catch (IOException e) { LOG.error(e.getMessage()); } - return msgs; } @@ -272,8 +289,8 @@ public String doAck(String ackId) throws IOException { StringEntity postBody = new StringEntity("{\"ackIds\":[" + ackId + "]}"); postBody.setContentType("application/json"); postAck.setEntity(postBody); - - // check for proxy + + // check for proxy if (this.proxy != null) { postAck.setConfig(createProxyCfg()); } @@ -298,10 +315,11 @@ public String doAck(String ackId) throws IOException { resMsg = result.toString(); isRdr.close(); + } else { + // Log any api errors + logIssue(response); } - response.close(); - // Return a resposeMessage return resMsg; diff --git a/flink_jobs/batch_ar/src/main/java/argo/avro/MetricData.java b/flink_jobs/batch_ar/src/main/java/argo/avro/MetricData.java index 6868f656..77800770 100644 --- a/flink_jobs/batch_ar/src/main/java/argo/avro/MetricData.java +++ b/flink_jobs/batch_ar/src/main/java/argo/avro/MetricData.java @@ -1,13 +1,17 @@ /** * Autogenerated by Avro - * + * * DO NOT EDIT DIRECTLY */ -package argo.avro; +package argo.avro; + +import org.apache.avro.specific.SpecificData; + @SuppressWarnings("all") @org.apache.avro.specific.AvroGenerated public class MetricData extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { - public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); + private static final long serialVersionUID = 3861438289744595870L; + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"actual_data\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"default\":null},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } @Deprecated public java.lang.String timestamp; @Deprecated public java.lang.String service; @@ -15,32 +19,46 @@ public class MetricData extends org.apache.avro.specific.SpecificRecordBase impl @Deprecated public java.lang.String metric; @Deprecated public java.lang.String status; @Deprecated public java.lang.String monitoring_host; + @Deprecated public java.lang.String actual_data; @Deprecated public java.lang.String summary; @Deprecated public java.lang.String message; @Deprecated public java.util.Map tags; /** - * Default constructor. + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use newBuilder(). */ public MetricData() {} /** * All-args constructor. + * @param timestamp The new value for timestamp + * @param service The new value for service + * @param hostname The new value for hostname + * @param metric The new value for metric + * @param status The new value for status + * @param monitoring_host The new value for monitoring_host + * @param actual_data The new value for actual_data + * @param summary The new value for summary + * @param message The new value for message + * @param tags The new value for tags */ - public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String summary, java.lang.String message, java.util.Map tags) { + public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String actual_data, java.lang.String summary, java.lang.String message, java.util.Map tags) { this.timestamp = timestamp; this.service = service; this.hostname = hostname; this.metric = metric; this.status = status; this.monitoring_host = monitoring_host; + this.actual_data = actual_data; this.summary = summary; this.message = message; this.tags = tags; } public org.apache.avro.Schema getSchema() { return SCHEMA$; } - // Used by DatumWriter. Applications should not call. + // Used by DatumWriter. Applications should not call. public java.lang.Object get(int field$) { switch (field$) { case 0: return timestamp; @@ -49,13 +67,15 @@ public java.lang.Object get(int field$) { case 3: return metric; case 4: return status; case 5: return monitoring_host; - case 6: return summary; - case 7: return message; - case 8: return tags; + case 6: return actual_data; + case 7: return summary; + case 8: return message; + case 9: return tags; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } - // Used by DatumReader. Applications should not call. + + // Used by DatumReader. Applications should not call. @SuppressWarnings(value="unchecked") public void put(int field$, java.lang.Object value$) { switch (field$) { @@ -65,15 +85,17 @@ public void put(int field$, java.lang.Object value$) { case 3: metric = (java.lang.String)value$; break; case 4: status = (java.lang.String)value$; break; case 5: monitoring_host = (java.lang.String)value$; break; - case 6: summary = (java.lang.String)value$; break; - case 7: message = (java.lang.String)value$; break; - case 8: tags = (java.util.Map)value$; break; + case 6: actual_data = (java.lang.String)value$; break; + case 7: summary = (java.lang.String)value$; break; + case 8: message = (java.lang.String)value$; break; + case 9: tags = (java.util.Map)value$; break; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } /** * Gets the value of the 'timestamp' field. + * @return The value of the 'timestamp' field. */ public java.lang.String getTimestamp() { return timestamp; @@ -89,6 +111,7 @@ public void setTimestamp(java.lang.String value) { /** * Gets the value of the 'service' field. + * @return The value of the 'service' field. */ public java.lang.String getService() { return service; @@ -104,6 +127,7 @@ public void setService(java.lang.String value) { /** * Gets the value of the 'hostname' field. + * @return The value of the 'hostname' field. */ public java.lang.String getHostname() { return hostname; @@ -119,6 +143,7 @@ public void setHostname(java.lang.String value) { /** * Gets the value of the 'metric' field. + * @return The value of the 'metric' field. */ public java.lang.String getMetric() { return metric; @@ -134,6 +159,7 @@ public void setMetric(java.lang.String value) { /** * Gets the value of the 'status' field. + * @return The value of the 'status' field. */ public java.lang.String getStatus() { return status; @@ -149,6 +175,7 @@ public void setStatus(java.lang.String value) { /** * Gets the value of the 'monitoring_host' field. + * @return The value of the 'monitoring_host' field. */ public java.lang.String getMonitoringHost() { return monitoring_host; @@ -162,8 +189,25 @@ public void setMonitoringHost(java.lang.String value) { this.monitoring_host = value; } + /** + * Gets the value of the 'actual_data' field. + * @return The value of the 'actual_data' field. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value the value to set. + */ + public void setActualData(java.lang.String value) { + this.actual_data = value; + } + /** * Gets the value of the 'summary' field. + * @return The value of the 'summary' field. */ public java.lang.String getSummary() { return summary; @@ -179,6 +223,7 @@ public void setSummary(java.lang.String value) { /** * Gets the value of the 'message' field. + * @return The value of the 'message' field. */ public java.lang.String getMessage() { return message; @@ -194,6 +239,7 @@ public void setMessage(java.lang.String value) { /** * Gets the value of the 'tags' field. + * @return The value of the 'tags' field. */ public java.util.Map getTags() { return tags; @@ -207,21 +253,32 @@ public void setTags(java.util.Map value) { this.tags = value; } - /** Creates a new MetricData RecordBuilder */ + /** + * Creates a new MetricData RecordBuilder. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder() { return new argo.avro.MetricData.Builder(); } - - /** Creates a new MetricData RecordBuilder by copying an existing Builder */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData.Builder other) { return new argo.avro.MetricData.Builder(other); } - - /** Creates a new MetricData RecordBuilder by copying an existing MetricData instance */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing MetricData instance. + * @param other The existing instance to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData other) { return new argo.avro.MetricData.Builder(other); } - + /** * RecordBuilder for MetricData instances. */ @@ -234,23 +291,70 @@ public static class Builder extends org.apache.avro.specific.SpecificRecordBuild private java.lang.String metric; private java.lang.String status; private java.lang.String monitoring_host; + private java.lang.String actual_data; private java.lang.String summary; private java.lang.String message; private java.util.Map tags; /** Creates a new Builder */ private Builder() { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); } - - /** Creates a Builder by copying an existing Builder */ + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ private Builder(argo.avro.MetricData.Builder other) { super(other); + if (isValidValue(fields()[0], other.timestamp)) { + this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.service)) { + this.service = data().deepCopy(fields()[1].schema(), other.service); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.hostname)) { + this.hostname = data().deepCopy(fields()[2].schema(), other.hostname); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.metric)) { + this.metric = data().deepCopy(fields()[3].schema(), other.metric); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.status)) { + this.status = data().deepCopy(fields()[4].schema(), other.status); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.monitoring_host)) { + this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); + fieldSetFlags()[5] = true; + } + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); + fieldSetFlags()[6] = true; + } + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); + fieldSetFlags()[7] = true; + } + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); + fieldSetFlags()[8] = true; + } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - - /** Creates a Builder by copying an existing MetricData instance */ + + /** + * Creates a Builder by copying an existing MetricData instance + * @param other The existing instance to copy. + */ private Builder(argo.avro.MetricData other) { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); if (isValidValue(fields()[0], other.timestamp)) { this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); fieldSetFlags()[0] = true; @@ -275,242 +379,411 @@ private Builder(argo.avro.MetricData other) { this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); fieldSetFlags()[5] = true; } - if (isValidValue(fields()[6], other.summary)) { - this.summary = data().deepCopy(fields()[6].schema(), other.summary); + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); fieldSetFlags()[6] = true; } - if (isValidValue(fields()[7], other.message)) { - this.message = data().deepCopy(fields()[7].schema(), other.message); + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); fieldSetFlags()[7] = true; } - if (isValidValue(fields()[8], other.tags)) { - this.tags = data().deepCopy(fields()[8].schema(), other.tags); + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); fieldSetFlags()[8] = true; } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - /** Gets the value of the 'timestamp' field */ + /** + * Gets the value of the 'timestamp' field. + * @return The value. + */ public java.lang.String getTimestamp() { return timestamp; } - - /** Sets the value of the 'timestamp' field */ + + /** + * Sets the value of the 'timestamp' field. + * @param value The value of 'timestamp'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTimestamp(java.lang.String value) { validate(fields()[0], value); this.timestamp = value; fieldSetFlags()[0] = true; - return this; + return this; } - - /** Checks whether the 'timestamp' field has been set */ + + /** + * Checks whether the 'timestamp' field has been set. + * @return True if the 'timestamp' field has been set, false otherwise. + */ public boolean hasTimestamp() { return fieldSetFlags()[0]; } - - /** Clears the value of the 'timestamp' field */ + + + /** + * Clears the value of the 'timestamp' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTimestamp() { timestamp = null; fieldSetFlags()[0] = false; return this; } - /** Gets the value of the 'service' field */ + /** + * Gets the value of the 'service' field. + * @return The value. + */ public java.lang.String getService() { return service; } - - /** Sets the value of the 'service' field */ + + /** + * Sets the value of the 'service' field. + * @param value The value of 'service'. + * @return This builder. + */ public argo.avro.MetricData.Builder setService(java.lang.String value) { validate(fields()[1], value); this.service = value; fieldSetFlags()[1] = true; - return this; + return this; } - - /** Checks whether the 'service' field has been set */ + + /** + * Checks whether the 'service' field has been set. + * @return True if the 'service' field has been set, false otherwise. + */ public boolean hasService() { return fieldSetFlags()[1]; } - - /** Clears the value of the 'service' field */ + + + /** + * Clears the value of the 'service' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearService() { service = null; fieldSetFlags()[1] = false; return this; } - /** Gets the value of the 'hostname' field */ + /** + * Gets the value of the 'hostname' field. + * @return The value. + */ public java.lang.String getHostname() { return hostname; } - - /** Sets the value of the 'hostname' field */ + + /** + * Sets the value of the 'hostname' field. + * @param value The value of 'hostname'. + * @return This builder. + */ public argo.avro.MetricData.Builder setHostname(java.lang.String value) { validate(fields()[2], value); this.hostname = value; fieldSetFlags()[2] = true; - return this; + return this; } - - /** Checks whether the 'hostname' field has been set */ + + /** + * Checks whether the 'hostname' field has been set. + * @return True if the 'hostname' field has been set, false otherwise. + */ public boolean hasHostname() { return fieldSetFlags()[2]; } - - /** Clears the value of the 'hostname' field */ + + + /** + * Clears the value of the 'hostname' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearHostname() { hostname = null; fieldSetFlags()[2] = false; return this; } - /** Gets the value of the 'metric' field */ + /** + * Gets the value of the 'metric' field. + * @return The value. + */ public java.lang.String getMetric() { return metric; } - - /** Sets the value of the 'metric' field */ + + /** + * Sets the value of the 'metric' field. + * @param value The value of 'metric'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMetric(java.lang.String value) { validate(fields()[3], value); this.metric = value; fieldSetFlags()[3] = true; - return this; + return this; } - - /** Checks whether the 'metric' field has been set */ + + /** + * Checks whether the 'metric' field has been set. + * @return True if the 'metric' field has been set, false otherwise. + */ public boolean hasMetric() { return fieldSetFlags()[3]; } - - /** Clears the value of the 'metric' field */ + + + /** + * Clears the value of the 'metric' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMetric() { metric = null; fieldSetFlags()[3] = false; return this; } - /** Gets the value of the 'status' field */ + /** + * Gets the value of the 'status' field. + * @return The value. + */ public java.lang.String getStatus() { return status; } - - /** Sets the value of the 'status' field */ + + /** + * Sets the value of the 'status' field. + * @param value The value of 'status'. + * @return This builder. + */ public argo.avro.MetricData.Builder setStatus(java.lang.String value) { validate(fields()[4], value); this.status = value; fieldSetFlags()[4] = true; - return this; + return this; } - - /** Checks whether the 'status' field has been set */ + + /** + * Checks whether the 'status' field has been set. + * @return True if the 'status' field has been set, false otherwise. + */ public boolean hasStatus() { return fieldSetFlags()[4]; } - - /** Clears the value of the 'status' field */ + + + /** + * Clears the value of the 'status' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearStatus() { status = null; fieldSetFlags()[4] = false; return this; } - /** Gets the value of the 'monitoring_host' field */ + /** + * Gets the value of the 'monitoring_host' field. + * @return The value. + */ public java.lang.String getMonitoringHost() { return monitoring_host; } - - /** Sets the value of the 'monitoring_host' field */ + + /** + * Sets the value of the 'monitoring_host' field. + * @param value The value of 'monitoring_host'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMonitoringHost(java.lang.String value) { validate(fields()[5], value); this.monitoring_host = value; fieldSetFlags()[5] = true; - return this; + return this; } - - /** Checks whether the 'monitoring_host' field has been set */ + + /** + * Checks whether the 'monitoring_host' field has been set. + * @return True if the 'monitoring_host' field has been set, false otherwise. + */ public boolean hasMonitoringHost() { return fieldSetFlags()[5]; } - - /** Clears the value of the 'monitoring_host' field */ + + + /** + * Clears the value of the 'monitoring_host' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMonitoringHost() { monitoring_host = null; fieldSetFlags()[5] = false; return this; } - /** Gets the value of the 'summary' field */ + /** + * Gets the value of the 'actual_data' field. + * @return The value. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value The value of 'actual_data'. + * @return This builder. + */ + public argo.avro.MetricData.Builder setActualData(java.lang.String value) { + validate(fields()[6], value); + this.actual_data = value; + fieldSetFlags()[6] = true; + return this; + } + + /** + * Checks whether the 'actual_data' field has been set. + * @return True if the 'actual_data' field has been set, false otherwise. + */ + public boolean hasActualData() { + return fieldSetFlags()[6]; + } + + + /** + * Clears the value of the 'actual_data' field. + * @return This builder. + */ + public argo.avro.MetricData.Builder clearActualData() { + actual_data = null; + fieldSetFlags()[6] = false; + return this; + } + + /** + * Gets the value of the 'summary' field. + * @return The value. + */ public java.lang.String getSummary() { return summary; } - - /** Sets the value of the 'summary' field */ + + /** + * Sets the value of the 'summary' field. + * @param value The value of 'summary'. + * @return This builder. + */ public argo.avro.MetricData.Builder setSummary(java.lang.String value) { - validate(fields()[6], value); + validate(fields()[7], value); this.summary = value; - fieldSetFlags()[6] = true; - return this; + fieldSetFlags()[7] = true; + return this; } - - /** Checks whether the 'summary' field has been set */ + + /** + * Checks whether the 'summary' field has been set. + * @return True if the 'summary' field has been set, false otherwise. + */ public boolean hasSummary() { - return fieldSetFlags()[6]; + return fieldSetFlags()[7]; } - - /** Clears the value of the 'summary' field */ + + + /** + * Clears the value of the 'summary' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearSummary() { summary = null; - fieldSetFlags()[6] = false; + fieldSetFlags()[7] = false; return this; } - /** Gets the value of the 'message' field */ + /** + * Gets the value of the 'message' field. + * @return The value. + */ public java.lang.String getMessage() { return message; } - - /** Sets the value of the 'message' field */ + + /** + * Sets the value of the 'message' field. + * @param value The value of 'message'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMessage(java.lang.String value) { - validate(fields()[7], value); + validate(fields()[8], value); this.message = value; - fieldSetFlags()[7] = true; - return this; + fieldSetFlags()[8] = true; + return this; } - - /** Checks whether the 'message' field has been set */ + + /** + * Checks whether the 'message' field has been set. + * @return True if the 'message' field has been set, false otherwise. + */ public boolean hasMessage() { - return fieldSetFlags()[7]; + return fieldSetFlags()[8]; } - - /** Clears the value of the 'message' field */ + + + /** + * Clears the value of the 'message' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMessage() { message = null; - fieldSetFlags()[7] = false; + fieldSetFlags()[8] = false; return this; } - /** Gets the value of the 'tags' field */ + /** + * Gets the value of the 'tags' field. + * @return The value. + */ public java.util.Map getTags() { return tags; } - - /** Sets the value of the 'tags' field */ + + /** + * Sets the value of the 'tags' field. + * @param value The value of 'tags'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTags(java.util.Map value) { - validate(fields()[8], value); + validate(fields()[9], value); this.tags = value; - fieldSetFlags()[8] = true; - return this; + fieldSetFlags()[9] = true; + return this; } - - /** Checks whether the 'tags' field has been set */ + + /** + * Checks whether the 'tags' field has been set. + * @return True if the 'tags' field has been set, false otherwise. + */ public boolean hasTags() { - return fieldSetFlags()[8]; + return fieldSetFlags()[9]; } - - /** Clears the value of the 'tags' field */ + + + /** + * Clears the value of the 'tags' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTags() { tags = null; - fieldSetFlags()[8] = false; + fieldSetFlags()[9] = false; return this; } @@ -524,13 +797,15 @@ public MetricData build() { record.metric = fieldSetFlags()[3] ? this.metric : (java.lang.String) defaultValue(fields()[3]); record.status = fieldSetFlags()[4] ? this.status : (java.lang.String) defaultValue(fields()[4]); record.monitoring_host = fieldSetFlags()[5] ? this.monitoring_host : (java.lang.String) defaultValue(fields()[5]); - record.summary = fieldSetFlags()[6] ? this.summary : (java.lang.String) defaultValue(fields()[6]); - record.message = fieldSetFlags()[7] ? this.message : (java.lang.String) defaultValue(fields()[7]); - record.tags = fieldSetFlags()[8] ? this.tags : (java.util.Map) defaultValue(fields()[8]); + record.actual_data = fieldSetFlags()[6] ? this.actual_data : (java.lang.String) defaultValue(fields()[6]); + record.summary = fieldSetFlags()[7] ? this.summary : (java.lang.String) defaultValue(fields()[7]); + record.message = fieldSetFlags()[8] ? this.message : (java.lang.String) defaultValue(fields()[8]); + record.tags = fieldSetFlags()[9] ? this.tags : (java.util.Map) defaultValue(fields()[9]); return record; } catch (Exception e) { throw new org.apache.avro.AvroRuntimeException(e); } } } + } diff --git a/flink_jobs/batch_ar/src/main/java/argo/batch/ArgoArBatch.java b/flink_jobs/batch_ar/src/main/java/argo/batch/ArgoArBatch.java index 987446a6..0fad529e 100644 --- a/flink_jobs/batch_ar/src/main/java/argo/batch/ArgoArBatch.java +++ b/flink_jobs/batch_ar/src/main/java/argo/batch/ArgoArBatch.java @@ -70,12 +70,23 @@ public static void main(String[] args) throws Exception { Path ggp = new Path(params.getRequired("ggp")); Path down = new Path(params.getRequired("downtimes")); Path weight = new Path(params.getRequired("weights")); + DataSource confDS = env.readTextFile(params.getRequired("conf")); DataSource opsDS = env.readTextFile(params.getRequired("ops")); DataSource aprDS = env.readTextFile(params.getRequired("apr")); DataSource recDS = env.readTextFile(params.getRequired("rec")); + // begin with empty threshold datasource + DataSource thrDS = env.fromElements(""); + // if threshold filepath has been defined in cli parameters + if (params.has("thr")){ + // read file and update threshold datasource + thrDS = env.readTextFile(params.getRequired("thr")); + } + + + ConfigManager confMgr = new ConfigManager(); confMgr.loadJsonString(confDS.collect()); @@ -123,12 +134,13 @@ public static void main(String[] args) throws Exception { // Discard unused data and attach endpoint group as information DataSet mdataTrimDS = mdataPrevTotalDS.flatMap(new PickEndpoints(params)) .withBroadcastSet(mpsDS, "mps").withBroadcastSet(egpDS, "egp").withBroadcastSet(ggpDS, "ggp") - .withBroadcastSet(aprDS, "apr").withBroadcastSet(recDS, "rec").withBroadcastSet(confDS, "conf"); + .withBroadcastSet(aprDS, "apr").withBroadcastSet(recDS, "rec").withBroadcastSet(confDS, "conf") + .withBroadcastSet(opsDS, "ops").withBroadcastSet(thrDS, "thr"); // Combine prev and todays metric data with the generated missing metric // data DataSet mdataTotalDS = mdataTrimDS.union(fillMissDS); - + // Create a dataset of metric timelines DataSet metricTimelinesDS = mdataTotalDS.groupBy("group","service", "hostname", "metric") .sortGroup("timestamp", Order.ASCENDING).reduceGroup(new CreateMetricTimeline(params)) diff --git a/flink_jobs/batch_ar/src/main/java/argo/batch/MonData.java b/flink_jobs/batch_ar/src/main/java/argo/batch/MonData.java index 95b63e3f..35bbcd23 100644 --- a/flink_jobs/batch_ar/src/main/java/argo/batch/MonData.java +++ b/flink_jobs/batch_ar/src/main/java/argo/batch/MonData.java @@ -15,6 +15,7 @@ public class MonData { private String monHost; private String summary; private String message; + private String actualData; public MonData(){ @@ -27,6 +28,7 @@ public MonData(){ this.monHost=""; this.summary=""; this.message=""; + this.actualData=""; } public String getGroup() { @@ -100,10 +102,19 @@ public String getMessage() { public void setMessage(String message) { this.message = message; } + + public String getActualData() { + return actualData; + } + + public void setActualData(String actualData) { + this.actualData = actualData; + } + public String toString() { return "(" + this.group + "," + this.service + "," + this.hostname + "," + this.metric + "," + this.status + "," - + this.timestamp + "," + this.monHost + "," + this.summary + "," + this.message + ")"; + + this.timestamp + "," + this.monHost + "," + this.summary + "," + this.message + "," + this.actualData + ")"; } } diff --git a/flink_jobs/batch_ar/src/main/java/argo/batch/PickEndpoints.java b/flink_jobs/batch_ar/src/main/java/argo/batch/PickEndpoints.java index aa831dae..0971ea67 100644 --- a/flink_jobs/batch_ar/src/main/java/argo/batch/PickEndpoints.java +++ b/flink_jobs/batch_ar/src/main/java/argo/batch/PickEndpoints.java @@ -4,7 +4,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; - +import java.util.Map; import org.apache.flink.api.common.functions.RichFlatMapFunction; @@ -21,6 +21,8 @@ import argo.avro.MetricData; import argo.avro.MetricProfile; import ops.ConfigManager; +import ops.OpsManager; +import ops.ThresholdManager; import sync.AggregationProfileManager; import sync.EndpointGroupManager; import sync.GroupGroupManager; @@ -50,12 +52,16 @@ public PickEndpoints(ParameterTool params){ private List apr; private List rec; private List conf; + private List thr; + private List ops; private MetricProfileManager mpsMgr; private EndpointGroupManager egpMgr; private GroupGroupManager ggpMgr; private AggregationProfileManager aprMgr; private RecomputationManager recMgr; private ConfigManager confMgr; + private OpsManager opsMgr; + private ThresholdManager thrMgr; private String egroupType; @@ -77,6 +83,8 @@ public void open(Configuration parameters) throws IOException, ParseException { this.apr = getRuntimeContext().getBroadcastVariable("apr"); this.rec = getRuntimeContext().getBroadcastVariable("rec"); this.conf = getRuntimeContext().getBroadcastVariable("conf"); + this.ops = getRuntimeContext().getBroadcastVariable("ops"); + this.thr = getRuntimeContext().getBroadcastVariable("thr"); // Initialize metric profile manager this.mpsMgr = new MetricProfileManager(); @@ -102,6 +110,17 @@ public void open(Configuration parameters) throws IOException, ParseException { // Initialize endpoint group type this.egroupType = this.confMgr.egroup; + + // Initialize Ops Manager + this.opsMgr = new OpsManager(); + this.opsMgr.loadJsonString(ops); + + // Initialize Threshold manager + this.thrMgr = new ThresholdManager(); + if (!this.thr.get(0).isEmpty()){ + this.thrMgr.parseJSON(this.thr.get(0)); + } + } @@ -150,17 +169,41 @@ public void flatMap(MetricData md, Collector out) throws Exception { for (String groupname : groupnames) { if (ggpMgr.checkSubGroup(groupname) == true){ + + String status = md.getStatus(); + String actualData = md.getActualData(); + + if (actualData != null) { + // Check for relevant rule + String rule = thrMgr.getMostRelevantRule(groupname, md.getHostname(), md.getMetric()); + // if rule is indeed found + if (rule != ""){ + // get the retrieved values from the actual data + Map values = thrMgr.getThresholdValues(actualData); + // calculate + String[] statusNext = thrMgr.getStatusByRuleAndValues(rule, this.opsMgr, "AND", values); + if (statusNext[0] == "") statusNext[0] = status; + LOG.info("{},{},{} data:({}) {} --> {}",groupname,md.getHostname(),md.getMetric(),values,status,statusNext[0]); + if (status != statusNext[0]) { + status = statusNext[0]; + } + } + + + } + MonData mn = new MonData(); mn.setGroup(groupname); mn.setHostname(hostname); mn.setService(service); mn.setMetric(metric); mn.setMonHost(monHost); - mn.setStatus(md.getStatus()); + mn.setStatus(status); mn.setTimestamp(ts); mn.setMessage(md.getMessage()); mn.setSummary(md.getSummary()); - + // transfer the actual data to the enriched monitoring data object + mn.setActualData(actualData); out.collect(mn); } diff --git a/flink_jobs/batch_ar/src/main/java/ops/ConfigManager.java b/flink_jobs/batch_ar/src/main/java/ops/ConfigManager.java index e2d65930..bdc3069a 100644 --- a/flink_jobs/batch_ar/src/main/java/ops/ConfigManager.java +++ b/flink_jobs/batch_ar/src/main/java/ops/ConfigManager.java @@ -5,8 +5,6 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.Map.Entry; -import java.util.Arrays; import java.util.List; import java.util.TreeMap; @@ -19,27 +17,25 @@ import com.google.gson.JsonParseException; import com.google.gson.JsonParser; -import argo.avro.Weight; public class ConfigManager { private static final Logger LOG = Logger.getLogger(ConfigManager.class.getName()); public String id; // report uuid reference - public String tenant; public String report; + public String tenant; public String egroup; // endpoint group public String ggroup; // group of groups - public String agroup; // alternative group public String weight; // weight factor type public TreeMap egroupTags; public TreeMap ggroupTags; public TreeMap mdataTags; public ConfigManager() { - this.tenant = null; this.report = null; this.id = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; @@ -51,8 +47,8 @@ public ConfigManager() { public void clear() { this.id = null; - this.tenant = null; this.report = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; @@ -74,6 +70,7 @@ public String getTenant() { return tenant; } + public String getEgroup() { return egroup; } @@ -90,31 +87,41 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jElement = jsonParser.parse(br); JsonObject jObj = jElement.getAsJsonObject(); // Get the simple fields - this.id = jObj.getAsJsonPrimitive("id").getAsString(); - this.tenant = jObj.getAsJsonPrimitive("tenant").getAsString(); - this.report = jObj.getAsJsonPrimitive("job").getAsString(); - this.egroup = jObj.getAsJsonPrimitive("egroup").getAsString(); - this.ggroup = jObj.getAsJsonPrimitive("ggroup").getAsString(); - this.weight = jObj.getAsJsonPrimitive("weight").getAsString(); - this.agroup = jObj.getAsJsonPrimitive("altg").getAsString(); - // Get compound fields - JsonObject jEgroupTags = jObj.getAsJsonObject("egroup_tags"); - JsonObject jGgroupTags = jObj.getAsJsonObject("ggroup_tags"); - JsonObject jMdataTags = jObj.getAsJsonObject("mdata_tags"); - - // Iterate fields - for (Entry item : jEgroupTags.entrySet()) { - - this.egroupTags.put(item.getKey(), item.getValue().getAsString()); + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + + // optional weight filtering + this.weight = ""; + if (jObj.has("weight")){ + this.weight = jObj.get("weight").getAsString(); } - for (Entry item : jGgroupTags.entrySet()) { - - this.ggroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jMdataTags.entrySet()) { - - this.mdataTags.put(item.getKey(), item.getValue().getAsString()); + // Get compound fields + JsonArray jTags = jObj.getAsJsonArray("filter_tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } } + } catch (FileNotFoundException ex) { LOG.error("Could not open file:" + jsonFile.getName()); @@ -146,30 +153,37 @@ public void loadJsonString(List confJson) throws JsonParseException { JsonElement jElement = jsonParser.parse(confJson.get(0)); JsonObject jObj = jElement.getAsJsonObject(); // Get the simple fields - this.id = jObj.getAsJsonPrimitive("id").getAsString(); - this.tenant = jObj.getAsJsonPrimitive("tenant").getAsString(); - this.report = jObj.getAsJsonPrimitive("job").getAsString(); - this.egroup = jObj.getAsJsonPrimitive("egroup").getAsString(); - this.ggroup = jObj.getAsJsonPrimitive("ggroup").getAsString(); - this.weight = jObj.getAsJsonPrimitive("weight").getAsString(); - this.agroup = jObj.getAsJsonPrimitive("altg").getAsString(); - // Get compound fields - JsonObject jEgroupTags = jObj.getAsJsonObject("egroup_tags"); - JsonObject jGgroupTags = jObj.getAsJsonObject("ggroup_tags"); - JsonObject jMdataTags = jObj.getAsJsonObject("mdata_tags"); - - // Iterate fields - for (Entry item : jEgroupTags.entrySet()) { - - this.egroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jGgroupTags.entrySet()) { - - this.ggroupTags.put(item.getKey(), item.getValue().getAsString()); + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + // optional weight filtering + this.weight = ""; + if (jObj.has("weight")){ + this.weight = jObj.get("weight").getAsString(); } - for (Entry item : jMdataTags.entrySet()) { - - this.mdataTags.put(item.getKey(), item.getValue().getAsString()); + // Get compound fields + JsonArray jTags = jObj.getAsJsonArray("tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } } } catch (JsonParseException ex) { diff --git a/flink_jobs/batch_ar/src/main/java/ops/ThresholdManager.java b/flink_jobs/batch_ar/src/main/java/ops/ThresholdManager.java new file mode 100644 index 00000000..e69a0190 --- /dev/null +++ b/flink_jobs/batch_ar/src/main/java/ops/ThresholdManager.java @@ -0,0 +1,754 @@ +package ops; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; + +/** + * @author kaggis + * + */ +public class ThresholdManager { + + private static final Logger LOG = Logger.getLogger(ThresholdManager.class.getName()); + + // Nested map that holds rule definitions: "groups/hosts/metrics" -> label -> + // threshold + // rules" + private Map> rules; + + // Reverse index checks for group, host, metrics + private HashSet metrics; + private HashSet hosts; + private HashSet groups; + private String aggregationOp = "AND"; + + public Map> getRules() { + return this.rules; + } + + /** + * Threshold class implements objects that hold threshold values as they are + * parsed by a threshold expression such as the following one: + * + * label=30s;0:50,50:100,0,100 + * + * A Threshold object can be directly constructed from a string including an + * expression as the above + * + * Each threshold object stores the threshold expression and the individual + * parsed items such as value, uom, warning range, critical range and min,max + * values + * + */ + class Threshold { + + private static final String defWarning = "WARNING"; + private static final String defCritical = "CRITICAL"; + + private String expression; + private String label; + private Float value; + private String uom; + private Range warning; + private Range critical; + private Float min; + private Float max; + + + + /** + * Constructs a threshold from a string containing a threshold expression + * + * @param expression + * A string containing a threshold exception as the following one: + * label=30s;0:50,50:100,0,100 + * + */ + public Threshold(String expression) { + Threshold temp = parseAndSet(expression); + this.expression = temp.expression; + this.label = temp.label; + this.value = temp.value; + this.uom = temp.uom; + this.warning = temp.warning; + this.critical = temp.critical; + this.min = temp.min; + this.max = temp.max; + + } + + /** + * Create a new threshold object by providing each parameter + * + * @param expression + * string containing the threshold expression + * @param label + * threshold label + * @param value + * threshold value + * @param uom + * unit of measurement - optional + * @param warning + * a range determining warning statuses + * @param critical + * a range determining critical statuses + * @param min + * minimum value available for this threshold + * @param max + * maximum value available for this threshold + */ + public Threshold(String expression, String label, float value, String uom, Range warning, Range critical, + float min, float max) { + + this.expression = expression; + this.label = label; + this.value = value; + this.uom = uom; + this.warning = warning; + this.critical = critical; + this.min = min; + this.max = max; + } + + public String getExpression() { + return expression; + } + + public String getLabel() { + return label; + } + + public float getValue() { + return value; + } + + public String getUom() { + return uom; + } + + public Range getWarning() { + return warning; + } + + public Range getCritical() { + return critical; + } + + public float getMin() { + return min; + } + + public float getMax() { + return max; + } + + /** + * Parses a threshold expression string and returns a Threshold object + * + * @param threshold + * string containing the threshold expression + * @return Threshold object + */ + public Threshold parseAndSet(String threshold) { + + String pThresh = threshold; + String curLabel = ""; + String curUom = ""; + Float curValue = Float.NaN; + Range curWarning = new Range(); // empty range + Range curCritical = new Range(); // emtpy range + Float curMin = Float.NaN; + Float curMax = Float.NaN; + // find label by splitting at = + String[] tokens = pThresh.split("="); + // Must have two tokens to continue, label=something + if (tokens.length == 2) { + curLabel = tokens[0]; + + // Split right value by ; to find the array of arguments + String[] subtokens = tokens[1].split(";"); + // Must have size > 0 at least a value + if (subtokens.length > 0) { + curUom = getUOM(subtokens[0]); + curValue = Float.parseFloat(subtokens[0].replaceAll(curUom, "")); + if (subtokens.length > 1) { + // iterate over rest of subtokens + for (int i = 1; i < subtokens.length; i++) { + if (i == 1) { + // parse warning range + curWarning = new Range(subtokens[i]); + continue; + } else if (i == 2) { + // parse critical + curCritical = new Range(subtokens[i]); + continue; + } else if (i == 3) { + // parse min + curMin = Float.parseFloat(subtokens[i]); + continue; + } else if (i == 4) { + // parse min + curMax = Float.parseFloat(subtokens[i]); + } + } + } + + } + + } + + return new Threshold(threshold, curLabel, curValue, curUom, curWarning, curCritical, curMin, curMax); + + } + + /** + * Reads a threshold string value and extracts the unit of measurement if + * present + * + * @param value + * String containing a representation of the value and uom + * @return String representing the uom. + */ + public String getUOM(String value) { + // check if ends with digit + if (Character.isDigit(value.charAt(value.length() - 1))) { + return ""; + } + + // check if ends with seconds + if (value.endsWith("s")) + return "s"; + if (value.endsWith("us")) + return "us"; + if (value.endsWith("ms")) + return "ms"; + if (value.endsWith("%")) + return "%"; + if (value.endsWith("B")) + return "B"; + if (value.endsWith("KB")) + return "KB"; + if (value.endsWith("MB")) + return "MB"; + if (value.endsWith("TB")) + return "TB"; + if (value.endsWith("c")) + return "c"; + + // Not valid range + throw new RuntimeException("Invalid Unit of measurement: " + value); + + } + + /** + * Checks an external value against a threshold's warning,critical ranges. If a + * range contains the value (warning or critical) the corresponding status is + * returned as string "WARNING" or "CRITICAL". If the threshold doesn't provide + * the needed data to decide on status an "" is returned back. + * + * @return string with the status result "WARNING", "CRITICAL" + */ + public String calcStatusWithValue(Float value) { + + if (!Float.isFinite(this.value)) + return ""; + if (!this.warning.isUndef()) { + if (this.warning.contains(value)) + return defWarning; + } + if (!this.critical.isUndef()) { + if (this.critical.contains(value)) + return defCritical; + } + + return ""; + } + + /** + * Checks a threshold's value against warning,critical ranges. If a range + * contains the value (warning or critical) the corresponding status is returned + * as string "WARNING" or "CRITICAL". If the threshold doesn't provide the + * needed data to decide on status an "" is returned back. + * + * @return string with the status result "WARNING", "CRITICAL" + */ + public String calcStatus() { + + if (!Float.isFinite(this.value)) + return ""; + if (!this.warning.isUndef()) { + if (this.warning.contains(this.value)) + return defWarning; + } + if (!this.critical.isUndef()) { + if (this.critical.contains(this.value)) + return defCritical; + } + + return ""; + } + + public String toString() { + String strWarn = ""; + String strCrit = ""; + String strMin = ""; + String strMax = ""; + + if (this.warning != null) + strWarn = this.warning.toString(); + if (this.critical != null) + strCrit = this.critical.toString(); + if (this.min != null) + strMin = this.min.toString(); + if (this.max != null) + strMax = this.max.toString(); + + return "[expression=" + this.expression + ", label=" + this.label + ", value=" + this.value + ", uom=" + + this.uom + ", warning=" + strWarn + ", critical=" + strCrit + ", min=" + strMin + ", max=" + + strMax + ")"; + } + + } + + /** + * Range implements a simple object that holds a threshold's critical or warning + * range. It includes a floor,ceil as floats and an exclude flag when a range is + * supposed to be used for exclusion and not inclusion. The threshold spec uses + * an '@' character in front of a range to define inversion(exclusion) + * + * Inclusion assumes that floor < value < ceil and not floor <= value <= ceil + * + */ + class Range { + Float floor; + Float ceil; + Boolean exclude; + + /** + * Creates an empty range. Invert is false and limits are NaN + */ + public Range() { + this.floor = Float.NaN; + this.ceil = Float.NaN; + this.exclude = false; + } + + /** + * Creates a range by parameters + * + * @param floor + * Float that defines the lower limit of the range + * @param ceil + * Float that defines the upper limit of the range + * @param exclude + * boolean that defines if the range is used for inclusion (true) or + * exlusion (false) + */ + public Range(Float floor, Float ceil, Boolean exclude) { + this.floor = floor; + this.ceil = ceil; + this.exclude = exclude; + } + + /** + * Creates a range by parsing a range expression string like the following one: + * '0:10' + * + * @param range + * string including a range expression + */ + public Range(String range) { + Range tmp = parseAndSet(range); + this.floor = tmp.floor; + this.ceil = tmp.ceil; + this.exclude = tmp.exclude; + } + + /** + * Checks if a Range is undefined (float,ceiling are NaN) + * + * @return boolean + */ + public boolean isUndef() { + return this.floor == Float.NaN || this.ceil == Float.NaN; + } + + /** + * Checks if a value is included in range (or truly excluded if range is an + * exclusion) + * + * @param value + * Float + * @return boolean + */ + public boolean contains(Float value) { + boolean result = value > this.floor && value < this.ceil; + if (this.exclude) { + return !result; + } + return result; + } + + /** + * Parses a range expression string and creates a Range object Range expressions + * can be in the following forms: + *
    + *
  • 10 - range starting from 0 to 10
  • + *
  • 10: - range starting from 10 to infinity
  • + *
  • ~:20 - range starting from negative inf. up to 20
  • + *
  • 20:30 - range between two numbers
  • + *
  • @20:30 - inverted range, excludes betweeen two numbers + *
+ * + * @param expression + * String containing a range expression + * @return + */ + public Range parseAndSet(String expression) { + String parsedRange = expression; + Float curFloor = 0F; + Float curCeil = 0F; + boolean curInv = false; + if (parsedRange.replaceAll(" ", "").equals("")) { + return new Range(); + } + // check if invert + if (parsedRange.startsWith("@")) { + curInv = true; + // after check remove @ from range string + parsedRange = parsedRange.replaceAll("^@", ""); + } + + // check if range string doesn't have separator : + if (!parsedRange.contains(":")) { + // then we are in the case of a single number like 10 + // which defines the rule 0 --> 10 so + curFloor = 0F; + curCeil = Float.parseFloat(parsedRange); + + return new Range(curFloor, curCeil, curInv); + } + + // check if range end with separator : + if (parsedRange.endsWith(":")) { + parsedRange = parsedRange.replaceAll(":$", ""); + // then we are in the case of a signle number like 10: + // which defines the rule 10 --> positive infinity + curFloor = Float.parseFloat(parsedRange); + curCeil = Float.POSITIVE_INFINITY; + return new Range(curFloor, curCeil, curInv); + } + + // tokenize string without prefixes + String[] tokens = parsedRange.split(":"); + if (tokens.length == 2) { + // check if token[0] is negative infinity ~ + if (tokens[0].equalsIgnoreCase("~")) { + curFloor = Float.NEGATIVE_INFINITY; + } else { + curFloor = Float.parseFloat(tokens[0]); + } + + curCeil = Float.parseFloat(tokens[1]); + return new Range(curFloor, curCeil, curInv); + } + + // Not valid range + throw new RuntimeException("Invalid threshold: " + expression); + + } + + public String toString() { + return "(floor=" + this.floor + ",ceil=" + this.ceil + ",invert=" + this.exclude.toString() + ")"; + } + + } + + /** + * Creates a Manager that parses rules files with thresholds and stores them + * internally as objects. A ThresholdManager can be used to automatically + * calculate statuses about a monitoring item (group,host,metric) based on the + * most relevant threshold rules stored in it. + */ + public ThresholdManager() { + + this.rules = new HashMap>(); + this.hosts = new HashSet(); + this.groups = new HashSet(); + this.metrics = new HashSet(); + + } + + /** + * Return the default operation when aggregating statuses generated from multiple threshold rules + * @return + */ + public String getAggregationOp() { + return this.aggregationOp; + } + + + /** + * @param op string with the name of the operation to be used in the aggregation (AND,OR,custom one) + */ + public void setAggregationOp(String op) { + this.aggregationOp = op; + } + + /** + * Returns a status calculation for a specific rule key Each rule key is defined + * as follows: 'group/host/metric' and leads to a threshold rule. Group and host + * parts are optional as such: 'group//metric' or '/host/metric' or '//metric' + * + * @param rule + * string containing a rule key + * @param opsMgr + * an OpsManager Object to handle status aggregations + * @param opType + * an OpsManager operation to be used (like 'OR', 'AND') + * @return string with status result + */ + public String getStatusByRule(String rule, OpsManager opsMgr, String opType) { + + if (!rules.containsKey(rule)) + return ""; + String status = ""; + Map tholds = rules.get(rule); + for (Entry thold : tholds.entrySet()) { + // first step + if (status == "") { + status = thold.getValue().calcStatus(); + continue; + } + String statusNext = thold.getValue().calcStatus(); + if (statusNext != "") { + status = opsMgr.op(opType, status, statusNext); + } + } + return status; + } + + /** + * Returns a status calculation for a specific rule key Each rule key is defined + * as follows: 'group/host/metric' and leads to a threshold rule. Group and host + * parts are optional as such: 'group//metric' or '/host/metric' or '//metric' + * + * @param rule + * string containing a rule key + * @param opsMgr + * an OpsManager Object to handle status aggregations + * @param opType + * an OpsManager operation to be used (like 'OR', 'AND') + * @return string array with two elements. First element is the status result and second one the rule applied + */ + public String[] getStatusByRuleAndValues(String rule, OpsManager opsMgr, String opType, Map values) { + + if (!rules.containsKey(rule)) + return new String[] {"",""}; + String status = ""; + String explain = ""; + Map tholds = rules.get(rule); + + for ( Entry value : values.entrySet()) { + String label = value.getKey(); + if (tholds.containsKey(label)) { + Threshold th = tholds.get(label); + // first step + if (status == "") { + + status = th.calcStatusWithValue(value.getValue()); + explain = th.getExpression(); + continue; + } + + String statusNext = th.calcStatusWithValue(value.getValue()); + + if (statusNext != "") { + status = opsMgr.op(opType, status, statusNext); + explain = explain + " " + th.getExpression(); + } + } + } + + + return new String[]{status,explain}; + + } + + /** + * Gets the most relevant rule based on a monitoring item (group,host,metric) + * using the following precedence (specific to least specific) (group, host, + * metric) #1 ( , host, metric) #2 (group, , metric) #3 ( , , metric) #4 + * + * @param group + * string with name of the monitored endpoint group + * @param host + * string with name of the monitored host + * @param metric + * string with name of the monitored metric + * @return a string with the relevant rule key + */ + public String getMostRelevantRule(String group, String host, String metric) { + if (!this.metrics.contains(metric)) { + return ""; // nothing found + } else { + + // order or precedence: more specific first + // group,host,metric #1 + // ,host,metric #2 + // group ,metric #3 + // ,metric #4 + if (this.hosts.contains(host)) { + if (this.groups.contains(group)) { + // check if combined entry indeed exists + String key = String.format("%s/%s/%s", group, host, metric); + if (this.rules.containsKey(key)) + return key; // #1 + + } else { + return String.format("/%s/%s", host, metric); // #2 + } + } + + if (this.groups.contains(group)) { + // check if combined entry indeed exists + String key = String.format("%s//%s", group, metric); // #3 + if (this.rules.containsKey(key)) + return key; + } + + return String.format("//%s", metric); + } + + } + + /** + * Parses an expression that might contain multiple labels=thresholds separated + * by whitespace and creates a HashMap of labels to parsed threshold objects + * + * @param thresholds + * an expression that might contain multiple thresholds + * @return a HashMap to Threshold objects + */ + public Map parseThresholds(String thresholds) { + Map subMap = new HashMap(); + // Tokenize with lookahead on the point when a new label starts + String[] tokens = thresholds.split("(;|[ ]+)(?=[a-zA-Z])"); + for (String token : tokens) { + Threshold curTh = new Threshold(token); + if (curTh != null) { + subMap.put(curTh.getLabel(), curTh); + } + } + return subMap; + } + + /** + * Parses an expression that might contain multiple labels=thresholds separated + * by whitespace and creates a HashMap of labels to parsed Float values + * + * @param thresholds + * an expression that might contain multiple thresholds + * @return a HashMap to Floats + */ + public Map getThresholdValues(String thresholds) { + Map subMap = new HashMap(); + // tokenize thresholds by whitespace + String[] tokens = thresholds.split("(;|[ ]+)(?=[a-zA-Z])"); + for (String token : tokens) { + Threshold curTh = new Threshold(token); + if (curTh != null) { + subMap.put(curTh.getLabel(), curTh.getValue()); + } + } + return subMap; + } + + /** + * Parses a JSON threshold rule file and populates the ThresholdManager + * + * @param jsonFile + * File to be parsed + * @return boolean signaling whether operation succeeded or not + */ + public boolean parseJSONFile(File jsonFile) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(jsonFile)); + String jsonStr = IOUtils.toString(br); + if (!parseJSON(jsonStr)) + return false; + + } catch (IOException ex) { + LOG.error("Could not open file:" + jsonFile.getName()); + return false; + + } catch (JsonParseException ex) { + LOG.error("File is not valid json:" + jsonFile.getName()); + return false; + } finally { + // Close quietly without exceptions the buffered reader + IOUtils.closeQuietly(br); + } + + return true; + + } + + /** + * Parses a json string with the appropriate threshold rule schema and populates + * the ThresholdManager + * + * @param jsonString + * string containing threshold rules in json format + * @return boolean signaling whether the parse information succeded or not + */ + public boolean parseJSON(String jsonString) { + + + JsonParser json_parser = new JsonParser(); + JsonObject jRoot = json_parser.parse(jsonString).getAsJsonObject(); + JsonArray jRules = jRoot.getAsJsonArray("rules"); + for (JsonElement jRule : jRules) { + JsonObject jRuleObj = jRule.getAsJsonObject(); + String ruleMetric = jRuleObj.getAsJsonPrimitive("metric").getAsString(); + String ruleHost = ""; + String ruleEgroup = ""; + + if (jRuleObj.has("host")) { + ruleHost = jRuleObj.getAsJsonPrimitive("host").getAsString(); + } + if (jRuleObj.has("endpoint_group")) { + ruleEgroup = jRuleObj.getAsJsonPrimitive("endpoint_group").getAsString(); + } + + String ruleThr = jRuleObj.getAsJsonPrimitive("thresholds").getAsString(); + this.metrics.add(ruleMetric); + if (ruleHost != "") + this.hosts.add(ruleHost); + if (ruleEgroup != "") + this.groups.add(ruleEgroup); + String full = ruleEgroup + "/" + ruleHost + "/" + ruleMetric; + Map thrMap = parseThresholds(ruleThr); + this.rules.put(full, thrMap); + } + + return true; + } + +} diff --git a/flink_jobs/batch_ar/src/main/java/sync/AggregationProfileManager.java b/flink_jobs/batch_ar/src/main/java/sync/AggregationProfileManager.java index 6c526407..11648956 100644 --- a/flink_jobs/batch_ar/src/main/java/sync/AggregationProfileManager.java +++ b/flink_jobs/batch_ar/src/main/java/sync/AggregationProfileManager.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map.Entry; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; @@ -235,28 +236,31 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jRootElement = jsonParser.parse(br); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } @@ -289,28 +293,32 @@ public void loadJsonString(List apsJson) throws IOException { JsonElement jRootElement = jsonParser.parse(apsJson.get(0)); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); + tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } diff --git a/flink_jobs/batch_ar/src/main/resources/ops/EGI-rules.json b/flink_jobs/batch_ar/src/main/resources/ops/EGI-rules.json new file mode 100644 index 00000000..2a52c99a --- /dev/null +++ b/flink_jobs/batch_ar/src/main/resources/ops/EGI-rules.json @@ -0,0 +1,33 @@ +{ + "rules": [ + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30" + }, + { + "metric": "org.bdii.Entries", + "thresholds": "time=-35s;~:10;15:;-100;300 entries=55;20;50:60;50;30" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s; entries=29;;30:50", + "host" : "bdii.host3.example.foo" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=29;0:10;20:30;0;30", + "host" : "bdii.host1.example.foo" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30", + "host" : "bdii.host1.example.foo", + "endpoint_group": "SITE-101" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30", + "endpoint_group": "SITE-101" + } + ] +} diff --git a/flink_jobs/batch_ar/src/main/resources/ops/ap1.json b/flink_jobs/batch_ar/src/main/resources/ops/ap1.json index 4940b57a..d754320c 100644 --- a/flink_jobs/batch_ar/src/main/resources/ops/ap1.json +++ b/flink_jobs/batch_ar/src/main/resources/ops/ap1.json @@ -1,35 +1,64 @@ { - - "name": "ap1", - "namespace": "test", - "metric_profile": "ch.cern.sam.ROC_CRITICAL", - "metric_ops":"AND", - "group_type": "sites", - "operation":"AND", - "groups": { - "compute": { - "services":{ - "CREAM-CE":"OR", - "ARC-CE":"OR", - "GRAM5":"OR", - "unicore6.TargetSystemFactory":"OR", - "QCG.Computing":"OR" - }, - "operation":"OR" - }, - "storage": { - "services":{ - "SRM":"OR", - "SRMv2":"OR" - }, - "operation":"OR" - }, - "information": { - "services":{ - "Site-BDII":"OR" - }, - "operation":"OR" - } - - } -} \ No newline at end of file + "id": "297c368a-524f-4144-9eb6-924fae5f08fa", + "name": "ap1", + "namespace": "test", + "endpoint_group": "sites", + "metric_operation": "AND", + "profile_operation": "AND", + "metric_profile": { + "name": "CH.CERN.SAM.ARGO_MON_CRITICAL", + "id": "c81fdb7b-d8f8-4ff9-96c5-6a0c336e2b25" + }, + "groups": [ + { + "name": "compute", + "operation": "OR", + "services": [ + { + "name": "CREAM-CE", + "operation": "OR" + }, + { + "name": "ARC-CE", + "operation": "OR" + }, + { + "name": "GRAM5", + "operation": "OR" + }, + { + "name": "unicore6.TargetSystemFactory", + "operation": "OR" + }, + { + "name": "QCG.Computing", + "operation": "OR" + } + ] + }, + { + "name": "storage", + "operation": "OR", + "services": [ + { + "name": "SRMv2", + "operation": "OR" + }, + { + "name": "SRM", + "operation": "OR" + } + ] + }, + { + "name": "information", + "operation": "OR", + "services": [ + { + "name": "Site-BDII", + "operation": "OR" + } + ] + } + ] + } diff --git a/flink_jobs/batch_ar/src/main/resources/ops/ap2.json b/flink_jobs/batch_ar/src/main/resources/ops/ap2.json index e7eb2c7c..fda7868f 100644 --- a/flink_jobs/batch_ar/src/main/resources/ops/ap2.json +++ b/flink_jobs/batch_ar/src/main/resources/ops/ap2.json @@ -1,35 +1,54 @@ { + "id": "337c368a-524f-4144-9eb6-924fae5f08fa", "name": "fedcloud", "namespace": "egi", - "metric_profile": "ch.cern.sam.CLOUD-MON", - "metric_ops":"AND", - "group_type": "sites", - "operation":"AND", - "groups": { - "accounting": { - "services":{ - "eu.egi.cloud.accounting":"OR" - }, - "operation":"OR" + "endpoint_group": "sites", + "metric_operation": "AND", + "profile_operation": "AND", + "metric_profile": { + "name": "ch.cern.sam.CLOUD-MON", + "id": "c88fdb7b-d8f8-4ff9-96c5-6a0c336e2b25" + }, + "groups": [ + { + "name": "accounting", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.accounting", + "operation": "OR" + } + ] }, - "information": { - "services":{ - "eu.egi.cloud.information.bdii":"OR" - }, - "operation":"OR" + { + "name": "information", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.information.bdii", + "operation": "OR" + } + ] }, - "storage-management": { - "services":{ - "eu.egi.cloud.storage-management.cdmi":"OR" - }, - "operation":"OR" + { + "name": "storage-management", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.storage-management.cdmi", + "operation": "OR" + } + ] }, - "vm-management": { - "services":{ - "eu.egi.cloud.vm-management.occi":"OR" - }, - "operation":"OR" + { + "name": "vm-management", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.vm-management.occi", + "operation": "OR" + } + ] } - - } + ] } diff --git a/flink_jobs/batch_ar/src/main/resources/ops/config.json b/flink_jobs/batch_ar/src/main/resources/ops/config.json index b7a83621..c2c550e5 100644 --- a/flink_jobs/batch_ar/src/main/resources/ops/config.json +++ b/flink_jobs/batch_ar/src/main/resources/ops/config.json @@ -1,24 +1,83 @@ { - "tenant":"EGI", - "id":"c800846f-8478-4af8-85d1-a3f12fe4c18f", - "job":"Critical", - "egroup":"SITES", - "ggroup":"NGI", - "altg":"ops", - "weight":"hepspec", - "egroup_tags":{ - "scope":"EGI", - "production":"1", - "monitored":"1" - }, - "ggroup_tags":{ - "scope":"EGI", - "infrastructure":"Production", - "certification":"Certified" - }, - "mdata_tags":{ - "vo":"ops", - "vo_fqan":"ops", - "roc":"any" + "id": "c800846f-8478-4af8-85d1-a3f12fe4c18f", + "info": { + "name": "Critical", + "description": "EGI report for Roc critical", + "created": "2015-10-19 10:35:49", + "updated": "2015-10-19 10:35:49" + }, + "tenant": "EGI", + "topology_schema": { + "group": { + "type": "NGI", + "group": { + "type": "SITES" + } + } + }, + "weight": "hepspec", + "profiles": [ + { + "id": "433beb2c-45cc-49d4-a8e0-b132bb30327e", + "name": "ch.cern.sam.ROC_CRITICAL", + "type": "metric" + }, + { + "id": "17d1462f-8f91-4728-a253-1a6e8e2e848d", + "name": "ops1", + "type": "operations" + }, + { + "id": "1ef8c0c9-f9ef-4ca1-9ee7-bb8b36332036", + "name": "critical", + "type": "aggregation" + } + ], + "filter_tags": [ + { + "name": "production", + "value": "1", + "context": "endpoint_groups" + }, + { + "name": "monitored", + "value": "1", + "context": "endpoint_groups" + }, + { + "name": "scope", + "value": "EGI", + "context": "endpoint_groups" + }, + { + "name": "scope", + "value": "EGI", + "context": "group_of_groups" + }, + { + "name": "infrastructure", + "value": "Production", + "context": "group_of_groups" + }, + { + "name": "certification", + "value": "Certified", + "context": "group_of_groups" + }, + { + "name": "vo", + "value": "ops", + "context": "metric_data" + }, + { + "name": "vo_fqan", + "value": "ops", + "context": "metric_data" + }, + { + "name": "roc", + "value": "any", + "context": "metric_data" + } + ] } -} diff --git a/flink_jobs/batch_ar/src/test/java/argo/batch/EndpointTimelineTest.java b/flink_jobs/batch_ar/src/test/java/argo/batch/EndpointTimelineTest.java index b522f751..e51124ee 100644 --- a/flink_jobs/batch_ar/src/test/java/argo/batch/EndpointTimelineTest.java +++ b/flink_jobs/batch_ar/src/test/java/argo/batch/EndpointTimelineTest.java @@ -49,31 +49,31 @@ public void test() throws URISyntaxException, IOException, ParseException { ArrayList mdata2 = new ArrayList(); ArrayList mdata3 = new ArrayList(); mdata1.add(new MetricData("2017-07-01T23:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata1.add(new MetricData("2017-07-02T05:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "WARNING", - "mon01.foo", "summary", "ok", null)); + "mon01.foo", "summary", "ok", null, null)); mdata1.add(new MetricData("2017-07-02T00:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata1.add(new MetricData("2017-07-02T12:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "CRITICAL", - "mon01.foo", "summary", "ok", null)); + "mon01.foo", "summary", "ok", null, null)); mdata1.add(new MetricData("2017-07-02T14:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata2.add(new MetricData("2017-07-01T23:00:00Z", "CREAM-CE", "cream01.foo", "job_cancel", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata2.add(new MetricData("2017-07-02T16:00:00Z", "CREAM-CE", "cream01.foo", "job_cancel", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata2.add(new MetricData("2017-07-02T19:00:00Z", "CREAM-CE", "cream01.foo", "job_cancel", "CRITICAL", - "mon01.foo", "summary", "ok", null)); + "mon01.foo", "summary", "ok", null, null)); mdata2.add(new MetricData("2017-07-02T20:00:00Z", "CREAM-CE", "cream01.foo", "job_cancel", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata3.add(new MetricData("2017-07-01T21:00:00Z", "CREAM-CE", "cream01.foo", "cert", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata3.add(new MetricData("2017-07-02T21:00:00Z", "CREAM-CE", "cream01.foo", "cert", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata3.add(new MetricData("2017-07-02T22:00:00Z", "CREAM-CE", "cream01.foo", "cert", "WARNING", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata3.add(new MetricData("2017-07-02T23:00:00Z", "CREAM-CE", "cream01.foo", "cert", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); // Create Frist Metric Timeline DTimeline dtl1 = new DTimeline(); diff --git a/flink_jobs/batch_ar/src/test/java/argo/batch/MetricTimelineTest.java b/flink_jobs/batch_ar/src/test/java/argo/batch/MetricTimelineTest.java index f3c0b738..0c91625c 100644 --- a/flink_jobs/batch_ar/src/test/java/argo/batch/MetricTimelineTest.java +++ b/flink_jobs/batch_ar/src/test/java/argo/batch/MetricTimelineTest.java @@ -47,15 +47,15 @@ public void test() throws URISyntaxException, IOException, ParseException { // List of MetricData ArrayList mdata = new ArrayList(); mdata.add(new MetricData("2017-07-01T23:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata.add(new MetricData("2017-07-02T05:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "WARNING", - "mon01.foo", "summary", "ok", null)); + "mon01.foo", "summary", "ok", null, null)); mdata.add(new MetricData("2017-07-02T00:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); mdata.add(new MetricData("2017-07-02T12:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "CRITICAL", - "mon01.foo", "summary", "ok", null)); + "mon01.foo", "summary", "ok", null, null)); mdata.add(new MetricData("2017-07-02T14:00:00Z", "CREAM-CE", "cream01.foo", "job_submit", "OK", "mon01.foo", - "summary", "ok", null)); + "summary", "ok", null, null)); DTimeline dtl = new DTimeline(); boolean first = true; diff --git a/flink_jobs/batch_ar/src/test/java/argo/batch/MultipleServiceGroupsTest.java b/flink_jobs/batch_ar/src/test/java/argo/batch/MultipleServiceGroupsTest.java index a5d98778..72a171c7 100644 --- a/flink_jobs/batch_ar/src/test/java/argo/batch/MultipleServiceGroupsTest.java +++ b/flink_jobs/batch_ar/src/test/java/argo/batch/MultipleServiceGroupsTest.java @@ -53,12 +53,14 @@ public void test() throws URISyntaxException, IOException, ParseException { monList.add(mn); } - String expOut = "[(SH,service.typeA,hostA,,,,,,), " + "(SH_N,service.typeA,hostA,,,,,,), " - + "(PROV,service.typeA,hostA,,,,,,), " + "(D,service.typeA,hostA,,,,,,), " - + "(SC,service.typeA,hostA,,,,,,), " + "(PR,service.typeA,hostA,,,,,,), " - + "(SH_R,service.typeA,hostA,,,,,,), " + "(OP,service.typeA,hostA,,,,,,), " - + "(SH_L,service.typeA,hostA,,,,,,), " + "(GT,service.typeA,hostA,,,,,,), " - + "(ORG_C,service.typeA,hostA,,,,,,)]"; + String expOut = "[(SH,service.typeA,hostA,,,,,,,), " + "(SH_N,service.typeA,hostA,,,,,,,), " + + "(PROV,service.typeA,hostA,,,,,,,), " + "(D,service.typeA,hostA,,,,,,,), " + + "(SC,service.typeA,hostA,,,,,,,), " + "(PR,service.typeA,hostA,,,,,,,), " + + "(SH_R,service.typeA,hostA,,,,,,,), " + "(OP,service.typeA,hostA,,,,,,,), " + + "(SH_L,service.typeA,hostA,,,,,,,), " + "(GT,service.typeA,hostA,,,,,,,), " + + "(ORG_C,service.typeA,hostA,,,,,,,)]"; + + assertEquals("multiple group test",expOut,Arrays.toString(monList.toArray())); diff --git a/flink_jobs/batch_ar/src/test/java/ops/ConfigManagerTest.java b/flink_jobs/batch_ar/src/test/java/ops/ConfigManagerTest.java index f6b13043..acae33c8 100644 --- a/flink_jobs/batch_ar/src/test/java/ops/ConfigManagerTest.java +++ b/flink_jobs/batch_ar/src/test/java/ops/ConfigManagerTest.java @@ -30,7 +30,7 @@ public void test() throws URISyntaxException, IOException { cfgMgr.loadJson(jsonFile); // Assert that the simple fields are loaded correctly - assertEquals("EGI", cfgMgr.tenant); + assertEquals("EGI",cfgMgr.getTenant()); assertEquals("Critical", cfgMgr.report); assertEquals("SITES", cfgMgr.egroup); assertEquals("NGI", cfgMgr.ggroup); diff --git a/flink_jobs/batch_ar/src/test/java/ops/ThresholdManagerTest.java b/flink_jobs/batch_ar/src/test/java/ops/ThresholdManagerTest.java new file mode 100644 index 00000000..ec7b1f84 --- /dev/null +++ b/flink_jobs/batch_ar/src/test/java/ops/ThresholdManagerTest.java @@ -0,0 +1,76 @@ +package ops; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.junit.BeforeClass; +import org.junit.Test; + +public class ThresholdManagerTest { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // Assert that files are present + assertNotNull("Test file missing", ThresholdManagerTest.class.getResource("/ops/EGI-algorithm.json")); + assertNotNull("Test file missing", ThresholdManagerTest.class.getResource("/ops/EGI-rules.json")); + } + + @Test + public void test() throws IOException, URISyntaxException { + + // Prepare Resource File + URL opsJsonFile = ThresholdManagerTest.class.getResource("/ops/EGI-algorithm.json"); + File opsFile = new File(opsJsonFile.toURI()); + // Instantiate class + OpsManager opsMgr = new OpsManager(); + // Test loading file + opsMgr.loadJson(opsFile); + + // Prepare Resource File + URL thrJsonFile = ThresholdManagerTest.class.getResource("/ops/EGI-rules.json"); + File thrFile = new File(thrJsonFile.toURI()); + // Instantiate class + ThresholdManager t = new ThresholdManager(); + t.parseJSONFile(thrFile); + + String[] expectedRules = new String[] { "//org.bdii.Freshness", "//org.bdii.Entries", + "/bdii.host1.example.foo/org.bdii.Freshness", "/bdii.host3.example.foo/org.bdii.Freshness", + "SITE-101/bdii.host1.example.foo/org.bdii.Freshness", "SITE-101//org.bdii.Freshness" }; + + assertEquals(expectedRules.length, t.getRules().entrySet().size()); + + for (String rule : expectedRules) { + assertEquals(true, t.getRules().keySet().contains(rule)); + } + + assertEquals("SITE-101/bdii.host1.example.foo/org.bdii.Freshness", + t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Freshness")); + + assertEquals("SITE-101//org.bdii.Freshness", + t.getMostRelevantRule("SITE-101", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Freshness", + t.getMostRelevantRule("SITE-202", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Freshness", + t.getMostRelevantRule("SITE-202", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Entries", + t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Entries")); + + assertEquals("", t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Foo")); + + assertEquals("WARNING", t.getStatusByRule("SITE-101/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "AND")); + assertEquals("CRITICAL", t.getStatusByRule("//org.bdii.Entries", opsMgr, "AND")); + assertEquals("WARNING", t.getStatusByRule("//org.bdii.Entries", opsMgr, "OR")); + assertEquals("CRITICAL", t.getStatusByRule("/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "AND")); + assertEquals("WARNING", t.getStatusByRule("/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "OR")); + assertEquals("",t.getStatusByRule("/bdii.host3.example.foo/org.bdii.Freshness", opsMgr, "AND")); //no critical or warning ranges defined + + } + +} diff --git a/flink_jobs/batch_status/pom.xml b/flink_jobs/batch_status/pom.xml index a9fae98e..6562ed49 100644 --- a/flink_jobs/batch_status/pom.xml +++ b/flink_jobs/batch_status/pom.xml @@ -126,6 +126,19 @@ log4j ${log4j.version} + + + junit-addons + junit-addons + 1.4 + test + + + junit + junit + 4.11 + test + diff --git a/flink_jobs/batch_status/src/main/java/argo/avro/MetricData.java b/flink_jobs/batch_status/src/main/java/argo/avro/MetricData.java index 6868f656..77800770 100644 --- a/flink_jobs/batch_status/src/main/java/argo/avro/MetricData.java +++ b/flink_jobs/batch_status/src/main/java/argo/avro/MetricData.java @@ -1,13 +1,17 @@ /** * Autogenerated by Avro - * + * * DO NOT EDIT DIRECTLY */ -package argo.avro; +package argo.avro; + +import org.apache.avro.specific.SpecificData; + @SuppressWarnings("all") @org.apache.avro.specific.AvroGenerated public class MetricData extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { - public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); + private static final long serialVersionUID = 3861438289744595870L; + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"MetricData\",\"namespace\":\"argo.avro\",\"fields\":[{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"service\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"hostname\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"metric\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"status\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"monitoring_host\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"actual_data\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"default\":null},{\"name\":\"summary\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"message\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}]},{\"name\":\"tags\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"avro.java.string\":\"String\"}]}]}"); public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } @Deprecated public java.lang.String timestamp; @Deprecated public java.lang.String service; @@ -15,32 +19,46 @@ public class MetricData extends org.apache.avro.specific.SpecificRecordBase impl @Deprecated public java.lang.String metric; @Deprecated public java.lang.String status; @Deprecated public java.lang.String monitoring_host; + @Deprecated public java.lang.String actual_data; @Deprecated public java.lang.String summary; @Deprecated public java.lang.String message; @Deprecated public java.util.Map tags; /** - * Default constructor. + * Default constructor. Note that this does not initialize fields + * to their default values from the schema. If that is desired then + * one should use newBuilder(). */ public MetricData() {} /** * All-args constructor. + * @param timestamp The new value for timestamp + * @param service The new value for service + * @param hostname The new value for hostname + * @param metric The new value for metric + * @param status The new value for status + * @param monitoring_host The new value for monitoring_host + * @param actual_data The new value for actual_data + * @param summary The new value for summary + * @param message The new value for message + * @param tags The new value for tags */ - public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String summary, java.lang.String message, java.util.Map tags) { + public MetricData(java.lang.String timestamp, java.lang.String service, java.lang.String hostname, java.lang.String metric, java.lang.String status, java.lang.String monitoring_host, java.lang.String actual_data, java.lang.String summary, java.lang.String message, java.util.Map tags) { this.timestamp = timestamp; this.service = service; this.hostname = hostname; this.metric = metric; this.status = status; this.monitoring_host = monitoring_host; + this.actual_data = actual_data; this.summary = summary; this.message = message; this.tags = tags; } public org.apache.avro.Schema getSchema() { return SCHEMA$; } - // Used by DatumWriter. Applications should not call. + // Used by DatumWriter. Applications should not call. public java.lang.Object get(int field$) { switch (field$) { case 0: return timestamp; @@ -49,13 +67,15 @@ public java.lang.Object get(int field$) { case 3: return metric; case 4: return status; case 5: return monitoring_host; - case 6: return summary; - case 7: return message; - case 8: return tags; + case 6: return actual_data; + case 7: return summary; + case 8: return message; + case 9: return tags; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } - // Used by DatumReader. Applications should not call. + + // Used by DatumReader. Applications should not call. @SuppressWarnings(value="unchecked") public void put(int field$, java.lang.Object value$) { switch (field$) { @@ -65,15 +85,17 @@ public void put(int field$, java.lang.Object value$) { case 3: metric = (java.lang.String)value$; break; case 4: status = (java.lang.String)value$; break; case 5: monitoring_host = (java.lang.String)value$; break; - case 6: summary = (java.lang.String)value$; break; - case 7: message = (java.lang.String)value$; break; - case 8: tags = (java.util.Map)value$; break; + case 6: actual_data = (java.lang.String)value$; break; + case 7: summary = (java.lang.String)value$; break; + case 8: message = (java.lang.String)value$; break; + case 9: tags = (java.util.Map)value$; break; default: throw new org.apache.avro.AvroRuntimeException("Bad index"); } } /** * Gets the value of the 'timestamp' field. + * @return The value of the 'timestamp' field. */ public java.lang.String getTimestamp() { return timestamp; @@ -89,6 +111,7 @@ public void setTimestamp(java.lang.String value) { /** * Gets the value of the 'service' field. + * @return The value of the 'service' field. */ public java.lang.String getService() { return service; @@ -104,6 +127,7 @@ public void setService(java.lang.String value) { /** * Gets the value of the 'hostname' field. + * @return The value of the 'hostname' field. */ public java.lang.String getHostname() { return hostname; @@ -119,6 +143,7 @@ public void setHostname(java.lang.String value) { /** * Gets the value of the 'metric' field. + * @return The value of the 'metric' field. */ public java.lang.String getMetric() { return metric; @@ -134,6 +159,7 @@ public void setMetric(java.lang.String value) { /** * Gets the value of the 'status' field. + * @return The value of the 'status' field. */ public java.lang.String getStatus() { return status; @@ -149,6 +175,7 @@ public void setStatus(java.lang.String value) { /** * Gets the value of the 'monitoring_host' field. + * @return The value of the 'monitoring_host' field. */ public java.lang.String getMonitoringHost() { return monitoring_host; @@ -162,8 +189,25 @@ public void setMonitoringHost(java.lang.String value) { this.monitoring_host = value; } + /** + * Gets the value of the 'actual_data' field. + * @return The value of the 'actual_data' field. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value the value to set. + */ + public void setActualData(java.lang.String value) { + this.actual_data = value; + } + /** * Gets the value of the 'summary' field. + * @return The value of the 'summary' field. */ public java.lang.String getSummary() { return summary; @@ -179,6 +223,7 @@ public void setSummary(java.lang.String value) { /** * Gets the value of the 'message' field. + * @return The value of the 'message' field. */ public java.lang.String getMessage() { return message; @@ -194,6 +239,7 @@ public void setMessage(java.lang.String value) { /** * Gets the value of the 'tags' field. + * @return The value of the 'tags' field. */ public java.util.Map getTags() { return tags; @@ -207,21 +253,32 @@ public void setTags(java.util.Map value) { this.tags = value; } - /** Creates a new MetricData RecordBuilder */ + /** + * Creates a new MetricData RecordBuilder. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder() { return new argo.avro.MetricData.Builder(); } - - /** Creates a new MetricData RecordBuilder by copying an existing Builder */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData.Builder other) { return new argo.avro.MetricData.Builder(other); } - - /** Creates a new MetricData RecordBuilder by copying an existing MetricData instance */ + + /** + * Creates a new MetricData RecordBuilder by copying an existing MetricData instance. + * @param other The existing instance to copy. + * @return A new MetricData RecordBuilder + */ public static argo.avro.MetricData.Builder newBuilder(argo.avro.MetricData other) { return new argo.avro.MetricData.Builder(other); } - + /** * RecordBuilder for MetricData instances. */ @@ -234,23 +291,70 @@ public static class Builder extends org.apache.avro.specific.SpecificRecordBuild private java.lang.String metric; private java.lang.String status; private java.lang.String monitoring_host; + private java.lang.String actual_data; private java.lang.String summary; private java.lang.String message; private java.util.Map tags; /** Creates a new Builder */ private Builder() { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); } - - /** Creates a Builder by copying an existing Builder */ + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ private Builder(argo.avro.MetricData.Builder other) { super(other); + if (isValidValue(fields()[0], other.timestamp)) { + this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); + fieldSetFlags()[0] = true; + } + if (isValidValue(fields()[1], other.service)) { + this.service = data().deepCopy(fields()[1].schema(), other.service); + fieldSetFlags()[1] = true; + } + if (isValidValue(fields()[2], other.hostname)) { + this.hostname = data().deepCopy(fields()[2].schema(), other.hostname); + fieldSetFlags()[2] = true; + } + if (isValidValue(fields()[3], other.metric)) { + this.metric = data().deepCopy(fields()[3].schema(), other.metric); + fieldSetFlags()[3] = true; + } + if (isValidValue(fields()[4], other.status)) { + this.status = data().deepCopy(fields()[4].schema(), other.status); + fieldSetFlags()[4] = true; + } + if (isValidValue(fields()[5], other.monitoring_host)) { + this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); + fieldSetFlags()[5] = true; + } + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); + fieldSetFlags()[6] = true; + } + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); + fieldSetFlags()[7] = true; + } + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); + fieldSetFlags()[8] = true; + } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - - /** Creates a Builder by copying an existing MetricData instance */ + + /** + * Creates a Builder by copying an existing MetricData instance + * @param other The existing instance to copy. + */ private Builder(argo.avro.MetricData other) { - super(argo.avro.MetricData.SCHEMA$); + super(SCHEMA$); if (isValidValue(fields()[0], other.timestamp)) { this.timestamp = data().deepCopy(fields()[0].schema(), other.timestamp); fieldSetFlags()[0] = true; @@ -275,242 +379,411 @@ private Builder(argo.avro.MetricData other) { this.monitoring_host = data().deepCopy(fields()[5].schema(), other.monitoring_host); fieldSetFlags()[5] = true; } - if (isValidValue(fields()[6], other.summary)) { - this.summary = data().deepCopy(fields()[6].schema(), other.summary); + if (isValidValue(fields()[6], other.actual_data)) { + this.actual_data = data().deepCopy(fields()[6].schema(), other.actual_data); fieldSetFlags()[6] = true; } - if (isValidValue(fields()[7], other.message)) { - this.message = data().deepCopy(fields()[7].schema(), other.message); + if (isValidValue(fields()[7], other.summary)) { + this.summary = data().deepCopy(fields()[7].schema(), other.summary); fieldSetFlags()[7] = true; } - if (isValidValue(fields()[8], other.tags)) { - this.tags = data().deepCopy(fields()[8].schema(), other.tags); + if (isValidValue(fields()[8], other.message)) { + this.message = data().deepCopy(fields()[8].schema(), other.message); fieldSetFlags()[8] = true; } + if (isValidValue(fields()[9], other.tags)) { + this.tags = data().deepCopy(fields()[9].schema(), other.tags); + fieldSetFlags()[9] = true; + } } - /** Gets the value of the 'timestamp' field */ + /** + * Gets the value of the 'timestamp' field. + * @return The value. + */ public java.lang.String getTimestamp() { return timestamp; } - - /** Sets the value of the 'timestamp' field */ + + /** + * Sets the value of the 'timestamp' field. + * @param value The value of 'timestamp'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTimestamp(java.lang.String value) { validate(fields()[0], value); this.timestamp = value; fieldSetFlags()[0] = true; - return this; + return this; } - - /** Checks whether the 'timestamp' field has been set */ + + /** + * Checks whether the 'timestamp' field has been set. + * @return True if the 'timestamp' field has been set, false otherwise. + */ public boolean hasTimestamp() { return fieldSetFlags()[0]; } - - /** Clears the value of the 'timestamp' field */ + + + /** + * Clears the value of the 'timestamp' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTimestamp() { timestamp = null; fieldSetFlags()[0] = false; return this; } - /** Gets the value of the 'service' field */ + /** + * Gets the value of the 'service' field. + * @return The value. + */ public java.lang.String getService() { return service; } - - /** Sets the value of the 'service' field */ + + /** + * Sets the value of the 'service' field. + * @param value The value of 'service'. + * @return This builder. + */ public argo.avro.MetricData.Builder setService(java.lang.String value) { validate(fields()[1], value); this.service = value; fieldSetFlags()[1] = true; - return this; + return this; } - - /** Checks whether the 'service' field has been set */ + + /** + * Checks whether the 'service' field has been set. + * @return True if the 'service' field has been set, false otherwise. + */ public boolean hasService() { return fieldSetFlags()[1]; } - - /** Clears the value of the 'service' field */ + + + /** + * Clears the value of the 'service' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearService() { service = null; fieldSetFlags()[1] = false; return this; } - /** Gets the value of the 'hostname' field */ + /** + * Gets the value of the 'hostname' field. + * @return The value. + */ public java.lang.String getHostname() { return hostname; } - - /** Sets the value of the 'hostname' field */ + + /** + * Sets the value of the 'hostname' field. + * @param value The value of 'hostname'. + * @return This builder. + */ public argo.avro.MetricData.Builder setHostname(java.lang.String value) { validate(fields()[2], value); this.hostname = value; fieldSetFlags()[2] = true; - return this; + return this; } - - /** Checks whether the 'hostname' field has been set */ + + /** + * Checks whether the 'hostname' field has been set. + * @return True if the 'hostname' field has been set, false otherwise. + */ public boolean hasHostname() { return fieldSetFlags()[2]; } - - /** Clears the value of the 'hostname' field */ + + + /** + * Clears the value of the 'hostname' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearHostname() { hostname = null; fieldSetFlags()[2] = false; return this; } - /** Gets the value of the 'metric' field */ + /** + * Gets the value of the 'metric' field. + * @return The value. + */ public java.lang.String getMetric() { return metric; } - - /** Sets the value of the 'metric' field */ + + /** + * Sets the value of the 'metric' field. + * @param value The value of 'metric'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMetric(java.lang.String value) { validate(fields()[3], value); this.metric = value; fieldSetFlags()[3] = true; - return this; + return this; } - - /** Checks whether the 'metric' field has been set */ + + /** + * Checks whether the 'metric' field has been set. + * @return True if the 'metric' field has been set, false otherwise. + */ public boolean hasMetric() { return fieldSetFlags()[3]; } - - /** Clears the value of the 'metric' field */ + + + /** + * Clears the value of the 'metric' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMetric() { metric = null; fieldSetFlags()[3] = false; return this; } - /** Gets the value of the 'status' field */ + /** + * Gets the value of the 'status' field. + * @return The value. + */ public java.lang.String getStatus() { return status; } - - /** Sets the value of the 'status' field */ + + /** + * Sets the value of the 'status' field. + * @param value The value of 'status'. + * @return This builder. + */ public argo.avro.MetricData.Builder setStatus(java.lang.String value) { validate(fields()[4], value); this.status = value; fieldSetFlags()[4] = true; - return this; + return this; } - - /** Checks whether the 'status' field has been set */ + + /** + * Checks whether the 'status' field has been set. + * @return True if the 'status' field has been set, false otherwise. + */ public boolean hasStatus() { return fieldSetFlags()[4]; } - - /** Clears the value of the 'status' field */ + + + /** + * Clears the value of the 'status' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearStatus() { status = null; fieldSetFlags()[4] = false; return this; } - /** Gets the value of the 'monitoring_host' field */ + /** + * Gets the value of the 'monitoring_host' field. + * @return The value. + */ public java.lang.String getMonitoringHost() { return monitoring_host; } - - /** Sets the value of the 'monitoring_host' field */ + + /** + * Sets the value of the 'monitoring_host' field. + * @param value The value of 'monitoring_host'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMonitoringHost(java.lang.String value) { validate(fields()[5], value); this.monitoring_host = value; fieldSetFlags()[5] = true; - return this; + return this; } - - /** Checks whether the 'monitoring_host' field has been set */ + + /** + * Checks whether the 'monitoring_host' field has been set. + * @return True if the 'monitoring_host' field has been set, false otherwise. + */ public boolean hasMonitoringHost() { return fieldSetFlags()[5]; } - - /** Clears the value of the 'monitoring_host' field */ + + + /** + * Clears the value of the 'monitoring_host' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMonitoringHost() { monitoring_host = null; fieldSetFlags()[5] = false; return this; } - /** Gets the value of the 'summary' field */ + /** + * Gets the value of the 'actual_data' field. + * @return The value. + */ + public java.lang.String getActualData() { + return actual_data; + } + + /** + * Sets the value of the 'actual_data' field. + * @param value The value of 'actual_data'. + * @return This builder. + */ + public argo.avro.MetricData.Builder setActualData(java.lang.String value) { + validate(fields()[6], value); + this.actual_data = value; + fieldSetFlags()[6] = true; + return this; + } + + /** + * Checks whether the 'actual_data' field has been set. + * @return True if the 'actual_data' field has been set, false otherwise. + */ + public boolean hasActualData() { + return fieldSetFlags()[6]; + } + + + /** + * Clears the value of the 'actual_data' field. + * @return This builder. + */ + public argo.avro.MetricData.Builder clearActualData() { + actual_data = null; + fieldSetFlags()[6] = false; + return this; + } + + /** + * Gets the value of the 'summary' field. + * @return The value. + */ public java.lang.String getSummary() { return summary; } - - /** Sets the value of the 'summary' field */ + + /** + * Sets the value of the 'summary' field. + * @param value The value of 'summary'. + * @return This builder. + */ public argo.avro.MetricData.Builder setSummary(java.lang.String value) { - validate(fields()[6], value); + validate(fields()[7], value); this.summary = value; - fieldSetFlags()[6] = true; - return this; + fieldSetFlags()[7] = true; + return this; } - - /** Checks whether the 'summary' field has been set */ + + /** + * Checks whether the 'summary' field has been set. + * @return True if the 'summary' field has been set, false otherwise. + */ public boolean hasSummary() { - return fieldSetFlags()[6]; + return fieldSetFlags()[7]; } - - /** Clears the value of the 'summary' field */ + + + /** + * Clears the value of the 'summary' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearSummary() { summary = null; - fieldSetFlags()[6] = false; + fieldSetFlags()[7] = false; return this; } - /** Gets the value of the 'message' field */ + /** + * Gets the value of the 'message' field. + * @return The value. + */ public java.lang.String getMessage() { return message; } - - /** Sets the value of the 'message' field */ + + /** + * Sets the value of the 'message' field. + * @param value The value of 'message'. + * @return This builder. + */ public argo.avro.MetricData.Builder setMessage(java.lang.String value) { - validate(fields()[7], value); + validate(fields()[8], value); this.message = value; - fieldSetFlags()[7] = true; - return this; + fieldSetFlags()[8] = true; + return this; } - - /** Checks whether the 'message' field has been set */ + + /** + * Checks whether the 'message' field has been set. + * @return True if the 'message' field has been set, false otherwise. + */ public boolean hasMessage() { - return fieldSetFlags()[7]; + return fieldSetFlags()[8]; } - - /** Clears the value of the 'message' field */ + + + /** + * Clears the value of the 'message' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearMessage() { message = null; - fieldSetFlags()[7] = false; + fieldSetFlags()[8] = false; return this; } - /** Gets the value of the 'tags' field */ + /** + * Gets the value of the 'tags' field. + * @return The value. + */ public java.util.Map getTags() { return tags; } - - /** Sets the value of the 'tags' field */ + + /** + * Sets the value of the 'tags' field. + * @param value The value of 'tags'. + * @return This builder. + */ public argo.avro.MetricData.Builder setTags(java.util.Map value) { - validate(fields()[8], value); + validate(fields()[9], value); this.tags = value; - fieldSetFlags()[8] = true; - return this; + fieldSetFlags()[9] = true; + return this; } - - /** Checks whether the 'tags' field has been set */ + + /** + * Checks whether the 'tags' field has been set. + * @return True if the 'tags' field has been set, false otherwise. + */ public boolean hasTags() { - return fieldSetFlags()[8]; + return fieldSetFlags()[9]; } - - /** Clears the value of the 'tags' field */ + + + /** + * Clears the value of the 'tags' field. + * @return This builder. + */ public argo.avro.MetricData.Builder clearTags() { tags = null; - fieldSetFlags()[8] = false; + fieldSetFlags()[9] = false; return this; } @@ -524,13 +797,15 @@ public MetricData build() { record.metric = fieldSetFlags()[3] ? this.metric : (java.lang.String) defaultValue(fields()[3]); record.status = fieldSetFlags()[4] ? this.status : (java.lang.String) defaultValue(fields()[4]); record.monitoring_host = fieldSetFlags()[5] ? this.monitoring_host : (java.lang.String) defaultValue(fields()[5]); - record.summary = fieldSetFlags()[6] ? this.summary : (java.lang.String) defaultValue(fields()[6]); - record.message = fieldSetFlags()[7] ? this.message : (java.lang.String) defaultValue(fields()[7]); - record.tags = fieldSetFlags()[8] ? this.tags : (java.util.Map) defaultValue(fields()[8]); + record.actual_data = fieldSetFlags()[6] ? this.actual_data : (java.lang.String) defaultValue(fields()[6]); + record.summary = fieldSetFlags()[7] ? this.summary : (java.lang.String) defaultValue(fields()[7]); + record.message = fieldSetFlags()[8] ? this.message : (java.lang.String) defaultValue(fields()[8]); + record.tags = fieldSetFlags()[9] ? this.tags : (java.util.Map) defaultValue(fields()[9]); return record; } catch (Exception e) { throw new org.apache.avro.AvroRuntimeException(e); } } } + } diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/ArgoStatusBatch.java b/flink_jobs/batch_status/src/main/java/argo/batch/ArgoStatusBatch.java index 53da2cc2..48f89c9b 100644 --- a/flink_jobs/batch_status/src/main/java/argo/batch/ArgoStatusBatch.java +++ b/flink_jobs/batch_status/src/main/java/argo/batch/ArgoStatusBatch.java @@ -11,8 +11,11 @@ import org.slf4j.Logger; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.flink.api.common.operators.Order; +import org.apache.flink.api.common.restartstrategy.RestartStrategies; +import org.apache.flink.api.common.time.Time; import org.apache.flink.api.java.DataSet; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.io.AvroInputFormat; @@ -54,6 +57,8 @@ public static void main(String[] args) throws Exception { // make parameters available in the web interface env.getConfig().setGlobalJobParameters(params); env.setParallelism(1); + // Fixed restart strategy: on failure attempt max 10 times to restart with a retry interval of 2 minutes + env.setRestartStrategy(RestartStrategies.fixedDelayRestart(10, Time.of(2, TimeUnit.MINUTES))); // sync data for input Path mps = new Path(params.getRequired("mps")); Path egp = new Path(params.getRequired("egp")); @@ -65,6 +70,14 @@ public static void main(String[] args) throws Exception { DataSource apsDS = env.readTextFile(params.getRequired("apr")); DataSource recDS = env.readTextFile(params.getRequired("rec")); + // begin with empty threshold datasource + DataSource thrDS = env.fromElements(""); + // if threshold filepath has been defined in cli parameters + if (params.has("thr")){ + // read file and update threshold datasource + thrDS = env.readTextFile(params.getRequired("thr")); + } + ConfigManager confMgr = new ConfigManager(); confMgr.loadJsonString(cfgDS.collect()); @@ -97,16 +110,28 @@ public static void main(String[] args) throws Exception { // Find the latest day DataSet pdataMin = pdataDS.groupBy("service", "hostname", "metric") .sortGroup("timestamp", Order.DESCENDING).first(1); - - DataSet mdataTotalDS = mdataDS.union(pdataMin); + + // Union todays data with the latest statuses from previous day + DataSet mdataPrevTotalDS = mdataDS.union(pdataMin); + + // Use yesterday's latest statuses and todays data to find the missing ones and add them to the mix + DataSet fillMissDS = mdataPrevTotalDS.reduceGroup(new FillMissing(params)) + .withBroadcastSet(mpsDS, "mps").withBroadcastSet(egpDS, "egp").withBroadcastSet(ggpDS, "ggp") + .withBroadcastSet(opsDS, "ops").withBroadcastSet(cfgDS, "conf"); + // Discard unused data and attach endpoint group as information - DataSet mdataTrimDS = mdataTotalDS.flatMap(new PickEndpoints(params)) + DataSet mdataTrimDS = mdataPrevTotalDS.flatMap(new PickEndpoints(params)) .withBroadcastSet(mpsDS, "mps").withBroadcastSet(egpDS, "egp").withBroadcastSet(ggpDS, "ggp") - .withBroadcastSet(recDS, "rec").withBroadcastSet(cfgDS, "conf"); + .withBroadcastSet(recDS, "rec").withBroadcastSet(cfgDS, "conf").withBroadcastSet(thrDS, "thr") + .withBroadcastSet(opsDS, "ops").withBroadcastSet(apsDS, "aps"); + // Combine prev and todays metric data with the generated missing metric + // data + DataSet mdataTotalDS = mdataTrimDS.union(fillMissDS); + // Create status detail data set - DataSet stDetailDS = mdataTrimDS.groupBy("group", "service", "hostname", "metric") + DataSet stDetailDS = mdataTotalDS.groupBy("group", "service", "hostname", "metric") .sortGroup("timestamp", Order.ASCENDING).reduceGroup(new CalcPrevStatus(params)) .withBroadcastSet(mpsDS, "mps").withBroadcastSet(egpDS, "egp").withBroadcastSet(ggpDS, "ggp"); diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/CalcPrevStatus.java b/flink_jobs/batch_status/src/main/java/argo/batch/CalcPrevStatus.java index f6d31594..b6282b0f 100644 --- a/flink_jobs/batch_status/src/main/java/argo/batch/CalcPrevStatus.java +++ b/flink_jobs/batch_status/src/main/java/argo/batch/CalcPrevStatus.java @@ -87,8 +87,6 @@ public void reduce(Iterable in, Collector out) throw } - - prevStatus = item.getStatus(); prevTimestamp = item.getTimestamp(); diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/FillMissing.java b/flink_jobs/batch_status/src/main/java/argo/batch/FillMissing.java new file mode 100644 index 00000000..93cc243b --- /dev/null +++ b/flink_jobs/batch_status/src/main/java/argo/batch/FillMissing.java @@ -0,0 +1,215 @@ +package argo.batch; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.flink.api.common.functions.RichGroupReduceFunction; +import org.apache.flink.api.java.tuple.Tuple4; +import org.apache.flink.api.java.utils.ParameterTool; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.util.Collector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import argo.avro.GroupEndpoint; +import argo.avro.GroupGroup; +import argo.avro.MetricData; +import argo.avro.MetricProfile; +import ops.ConfigManager; +import ops.OpsManager; +import sync.AggregationProfileManager; +import sync.EndpointGroupManager; +import sync.GroupGroupManager; +import sync.MetricProfileManager; + +/** + * Accepts a list of metric data objects and produces a list of missing mon data objects + */ +public class FillMissing extends RichGroupReduceFunction { + + private static final long serialVersionUID = 1L; + + final ParameterTool params; + + public FillMissing(ParameterTool params) { + this.params = params; + } + + static Logger LOG = LoggerFactory.getLogger(ArgoStatusBatch.class); + + private List mps; + private List ops; + private List egp; + private List ggp; + private List conf; + private MetricProfileManager mpsMgr; + private EndpointGroupManager egpMgr; + private GroupGroupManager ggpMgr; + private OpsManager opsMgr; + private ConfigManager confMgr; + private String runDate; + private String egroupType; + private Set> expected; + + /** + * Initialization method of the RichGroupReduceFunction operator + *

+ * This runs at the initialization of the operator and receives a + * configuration parameter object. It initializes all required structures + * used by this operator such as profile managers, operations managers, + * topology managers etc. + * + * @param parameters + * A flink Configuration object + */ + @Override + public void open(Configuration parameters) throws IOException { + + this.runDate = params.getRequired("run.date"); + // Get data from broadcast variables + this.mps = getRuntimeContext().getBroadcastVariable("mps"); + this.ops = getRuntimeContext().getBroadcastVariable("ops"); + this.egp = getRuntimeContext().getBroadcastVariable("egp"); + this.ggp = getRuntimeContext().getBroadcastVariable("ggp"); + this.conf = getRuntimeContext().getBroadcastVariable("conf"); + + // Initialize metric profile manager + this.mpsMgr = new MetricProfileManager(); + this.mpsMgr.loadFromList(mps); + // Initialize operations manager + this.opsMgr = new OpsManager(); + this.opsMgr.loadJsonString(ops); + + // Initialize endpoint group manager + this.egpMgr = new EndpointGroupManager(); + this.egpMgr.loadFromList(egp); + + this.ggpMgr = new GroupGroupManager(); + this.ggpMgr.loadFromList(ggp); + + this.confMgr = new ConfigManager(); + this.confMgr.loadJsonString(conf); + + this.runDate = params.getRequired("run.date"); + this.egroupType = this.confMgr.egroup; + + + + } + + + /** + * Reads the topology in endpoint group list and the metric profile and produces a set of available service endpoint metrics + * that are expected to be found (as tuple objects (endpoint_group,service,hostname,metric) + **/ + public void initExpected() { + this.expected = new HashSet>(); + String mProfile = this.mpsMgr.getProfiles().get(0); + for (GroupEndpoint servPoint: this.egp){ + + + ArrayList metrics = this.mpsMgr.getProfileServiceMetrics(mProfile, servPoint.getService()); + + if (metrics==null) continue; + for (String metric:metrics){ + this.expected.add(new Tuple4(servPoint.getGroup(),servPoint.getService(),servPoint.getHostname(),metric)); + } + + } + + + + + + } + + /** + * Iterates over all metric data and gathers a set of encountered service endpoint metrics. Then subtracts it from + * a set of expected service endpoint metrics (based on topology) so as the missing service endpoint metrics to be identified. Then based on the + * list of the missing service endpoint metrics corresponding metric data are created + * + * @param in + * An Iterable collection of MetricData objects + * @param out + * A Collector list of Missing MonData objects + */ + @Override + public void reduce(Iterable in, Collector out) throws Exception { + + initExpected(); + + Set> found = new HashSet>(); + + String service = ""; + String endpointGroup = ""; + String hostname = ""; + String metric = ""; + + String timestamp = this.runDate + "T00:00:00Z"; + String state = this.opsMgr.getDefaultMissing(); + + + for (MetricData item : in) { + + service = item.getService(); + hostname = item.getHostname(); + metric = item.getMetric(); + + // Filter By endpoint group if belongs to supergroup + ArrayList groupnames = egpMgr.getGroup(egroupType, hostname, service); + + for (String groupname : groupnames) { + if (ggpMgr.checkSubGroup(groupname) == true) { + endpointGroup = groupname; + found.add(new Tuple4(endpointGroup, service, hostname, metric)); + } + + } + + + + } + + + // Clone expected set to missing (because missing is going to be mutated after subtraction + Set> missing = new HashSet>(this.expected); + // The result of the subtraction is in missing set + missing.removeAll(found); + + + + + // For each item in missing create a missing metric data entry + for (Tuple4 item:missing){ + StatusMetric mn = new StatusMetric(); + // Create a StatusMetric output + // Grab the timestamp to generate the date and time integer fields + // that are exclusively used in datastore for indexing + String timestamp2 = timestamp.split("Z")[0]; + String[] tsToken = timestamp2.split("T"); + int dateInt = Integer.parseInt(tsToken[0].replace("-", "")); + int timeInt = Integer.parseInt(tsToken[1].replace(":","")); + mn.setGroup(item.f0); + mn.setService(item.f1); + mn.setHostname(item.f2); + mn.setMetric(item.f3); + mn.setStatus(state); + mn.setMessage(""); + mn.setSummary(""); + mn.setTimestamp(timestamp); + mn.setDateInt(dateInt); + mn.setTimeInt(timeInt); + + out.collect(mn); + + + } + + + + } + +} diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/MongoStatusOutput.java b/flink_jobs/batch_status/src/main/java/argo/batch/MongoStatusOutput.java index ae713355..65b52e98 100644 --- a/flink_jobs/batch_status/src/main/java/argo/batch/MongoStatusOutput.java +++ b/flink_jobs/batch_status/src/main/java/argo/batch/MongoStatusOutput.java @@ -121,7 +121,12 @@ private Document prepDoc(StatusMetric record) { .append("summary", record.getSummary()) .append("time_integer",record.getTimeInt()) .append("previous_state",record.getPrevState()) - .append("previous_ts", record.getPrevTs()); + .append("previous_timestamp", record.getPrevTs()) + // append the actual data to status metric record in datastore + .append("actual_data", record.getActualData()) + // append original status and threshold rule applied + .append("original_status", record.getOgStatus()) + .append("threshold_rule_applied", record.getRuleApplied()); } diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/PickEndpoints.java b/flink_jobs/batch_status/src/main/java/argo/batch/PickEndpoints.java index e3edd6eb..717af10a 100644 --- a/flink_jobs/batch_status/src/main/java/argo/batch/PickEndpoints.java +++ b/flink_jobs/batch_status/src/main/java/argo/batch/PickEndpoints.java @@ -4,6 +4,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.flink.api.common.functions.RichFilterFunction; import org.apache.flink.api.common.functions.RichFlatMapFunction; @@ -14,12 +15,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.esotericsoftware.minlog.Log; + import argo.avro.GroupEndpoint; import argo.avro.GroupGroup; import argo.avro.MetricData; import argo.avro.MetricProfile; import ops.ConfigManager; import ops.OpsManager; +import ops.ThresholdManager; import sync.AggregationProfileManager; import sync.EndpointGroupManager; import sync.GroupGroupManager; @@ -48,11 +52,17 @@ public PickEndpoints(ParameterTool params){ private List ggp; private List rec; private List cfg; + private List thr; + private List ops; + private List aps; + private OpsManager opsMgr; private MetricProfileManager mpsMgr; private EndpointGroupManager egpMgr; private GroupGroupManager ggpMgr; private RecomputationsManager recMgr; private ConfigManager cfgMgr; + private ThresholdManager thrMgr; + private AggregationProfileManager apsMgr; private String egroupType; @@ -65,6 +75,10 @@ public void open(Configuration parameters) throws IOException, ParseException { this.ggp = getRuntimeContext().getBroadcastVariable("ggp"); this.rec = getRuntimeContext().getBroadcastVariable("rec"); this.cfg = getRuntimeContext().getBroadcastVariable("conf"); + this.thr = getRuntimeContext().getBroadcastVariable("thr"); + this.ops = getRuntimeContext().getBroadcastVariable("ops"); + this.aps = getRuntimeContext().getBroadcastVariable("aps"); + // Initialize Recomputation manager this.recMgr = new RecomputationsManager(); @@ -84,7 +98,23 @@ public void open(Configuration parameters) throws IOException, ParseException { this.cfgMgr = new ConfigManager(); this.cfgMgr.loadJsonString(cfg); + // Initialize Ops Manager + this.opsMgr = new OpsManager(); + this.opsMgr.loadJsonString(ops); + + // Initialize Aggregation Profile manager + this.apsMgr = new AggregationProfileManager(); + this.apsMgr.loadJsonString(aps); + this.egroupType = cfgMgr.egroup; + + // Initialize Threshold manager + this.thrMgr = new ThresholdManager(); + if (!this.thr.get(0).isEmpty()){ + this.thrMgr.parseJSON(this.thr.get(0)); + } + + } @@ -93,6 +123,7 @@ public void open(Configuration parameters) throws IOException, ParseException { public void flatMap(MetricData md, Collector out) throws Exception { String prof = mpsMgr.getProfiles().get(0); + String aprof = apsMgr.getAvProfiles().get(0); String hostname = md.getHostname(); String service = md.getService(); String metric = md.getMetric(); @@ -102,10 +133,14 @@ public void flatMap(MetricData md, Collector out) throws Exception // Filter By monitoring engine if (recMgr.isMonExcluded(monHost, ts) == true) return; + // Filter By aggregation profile + if (apsMgr.checkService(aprof, service) == false) return; // Filter By metric profile if (mpsMgr.checkProfileServiceMetric(prof, service, metric) == false) return; - + + + // Filter By endpoint group if belongs to supergroup ArrayList groupnames = egpMgr.getGroup(egroupType, hostname, service); @@ -117,8 +152,33 @@ public void flatMap(MetricData md, Collector out) throws Exception String[] tsToken = timestamp2.split("T"); int dateInt = Integer.parseInt(tsToken[0].replace("-", "")); int timeInt = Integer.parseInt(tsToken[1].replace(":","")); + String status = md.getStatus(); + String actualData = md.getActualData(); + String ogStatus = ""; + String ruleApplied = ""; + + if (actualData != null) { + // Check for relevant rule + String rule = thrMgr.getMostRelevantRule(groupname, md.getHostname(), md.getMetric()); + // if rule is indeed found + if (rule != ""){ + // get the retrieved values from the actual data + Map values = thrMgr.getThresholdValues(actualData); + // calculate + String[] statusNext = thrMgr.getStatusByRuleAndValues(rule, this.opsMgr, "AND", values); + if (statusNext[0] == "") statusNext[0] = status; + LOG.info("{},{},{} data:({}) {} --> {}",groupname,md.getHostname(),md.getMetric(),values,status,statusNext[0]); + if (status != statusNext[0]) { + ogStatus = status; + ruleApplied = statusNext[1]; + status = statusNext[0]; + } + } + + + } - StatusMetric sm = new StatusMetric(groupname,md.getService(),md.getHostname(),md.getMetric(), md.getStatus(),md.getTimestamp(),dateInt,timeInt,md.getSummary(),md.getMessage(),"",""); + StatusMetric sm = new StatusMetric(groupname,md.getService(),md.getHostname(),md.getMetric(), status,md.getTimestamp(),dateInt,timeInt,md.getSummary(),md.getMessage(),"","",actualData, ogStatus, ruleApplied); out.collect(sm); } diff --git a/flink_jobs/batch_status/src/main/java/argo/batch/StatusMetric.java b/flink_jobs/batch_status/src/main/java/argo/batch/StatusMetric.java index acf315aa..9bf147c1 100644 --- a/flink_jobs/batch_status/src/main/java/argo/batch/StatusMetric.java +++ b/flink_jobs/batch_status/src/main/java/argo/batch/StatusMetric.java @@ -16,6 +16,9 @@ public class StatusMetric { private String message; private String prevState; private String prevTs; + private String actualData; + private String ogStatus; // original status from moniting host + private String ruleApplied; // threshold rule applied - empty if not public StatusMetric(){ this.group = ""; @@ -30,10 +33,13 @@ public StatusMetric(){ this.message = ""; this.prevState = ""; this.prevTs = ""; + this.actualData = ""; + this.ogStatus = ""; + this.ruleApplied = ""; } public StatusMetric(String group, String service, String hostname, String metric, String status, String timestamp, - int dateInt, int timeInt, String summary, String message, String prevState, String prevTs) { + int dateInt, int timeInt, String summary, String message, String prevState, String prevTs, String actualData, String ogStatus, String ruleApplied) { this.group = group; this.service = service; @@ -47,6 +53,9 @@ public StatusMetric(String group, String service, String hostname, String metric this.message = message; this.prevState = prevState; this.prevTs = prevTs; + this.actualData = actualData; + this.ogStatus = ogStatus; + this.ruleApplied = ruleApplied; } @@ -128,10 +137,31 @@ public void setPrevTs(String prevTs) { this.prevTs = prevTs; } + public String getActualData() { + return actualData; + } + public void setActualData(String actualData) { + this.actualData = actualData; + } + + public String getOgStatus() { + return ogStatus; + } + public void setOgStatus(String ogStatus) { + this.ogStatus = ogStatus; + } + + public String getRuleApplied() { + return ruleApplied; + } + public void setRuleApplied(String ruleApplied) { + this.ruleApplied = ruleApplied; + } + @Override public String toString() { return "(" + this.group + "," + this.service + "," + this.hostname + "," + this.metric + "," + this.status + "," + this.timestamp + "," + - this.dateInt + "," + this.timeInt + "," + this.prevState + "," + this.prevTs + ")"; + this.dateInt + "," + this.timeInt + "," + this.prevState + "," + this.prevTs + "," + this.actualData + "," + this.ogStatus + "," + this.ruleApplied + ")"; } } diff --git a/flink_jobs/batch_status/src/main/java/ops/ConfigManager.java b/flink_jobs/batch_status/src/main/java/ops/ConfigManager.java index 30af0f9d..bdc3069a 100644 --- a/flink_jobs/batch_status/src/main/java/ops/ConfigManager.java +++ b/flink_jobs/batch_status/src/main/java/ops/ConfigManager.java @@ -5,62 +5,59 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.Map.Entry; import java.util.List; import java.util.TreeMap; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; - - +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; + public class ConfigManager { private static final Logger LOG = Logger.getLogger(ConfigManager.class.getName()); public String id; // report uuid reference - public String tenant; public String report; + public String tenant; public String egroup; // endpoint group public String ggroup; // group of groups - public String agroup; // alternative group public String weight; // weight factor type public TreeMap egroupTags; public TreeMap ggroupTags; public TreeMap mdataTags; - public ConfigManager() { - this.tenant = null; this.report = null; this.id = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; this.egroupTags = new TreeMap(); this.ggroupTags = new TreeMap(); this.mdataTags = new TreeMap(); - + } public void clear() { - this.id=null; - this.tenant = null; + this.id = null; this.report = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; this.egroupTags.clear(); this.ggroupTags.clear(); this.mdataTags.clear(); - - } + } + public String getReportID() { return id; } @@ -73,10 +70,10 @@ public String getTenant() { return tenant; } + public String getEgroup() { return egroup; } - public void loadJson(File jsonFile) throws IOException { // Clear data @@ -90,30 +87,39 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jElement = jsonParser.parse(br); JsonObject jObj = jElement.getAsJsonObject(); // Get the simple fields - this.id = jObj.getAsJsonPrimitive("id").getAsString(); - this.tenant = jObj.getAsJsonPrimitive("tenant").getAsString(); - this.report = jObj.getAsJsonPrimitive("job").getAsString(); - this.egroup = jObj.getAsJsonPrimitive("egroup").getAsString(); - this.ggroup = jObj.getAsJsonPrimitive("ggroup").getAsString(); - this.weight = jObj.getAsJsonPrimitive("weight").getAsString(); - this.agroup = jObj.getAsJsonPrimitive("altg").getAsString(); - // Get compound fields - JsonObject jEgroupTags = jObj.getAsJsonObject("egroup_tags"); - JsonObject jGgroupTags = jObj.getAsJsonObject("ggroup_tags"); - JsonObject jMdataTags = jObj.getAsJsonObject("mdata_tags"); - JsonObject jDataMap = jObj.getAsJsonObject("datastore_maps"); - // Iterate fields - for (Entry item : jEgroupTags.entrySet()) { - - this.egroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jGgroupTags.entrySet()) { - - this.ggroupTags.put(item.getKey(), item.getValue().getAsString()); + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + + // optional weight filtering + this.weight = ""; + if (jObj.has("weight")){ + this.weight = jObj.get("weight").getAsString(); } - for (Entry item : jMdataTags.entrySet()) { - - this.mdataTags.put(item.getKey(), item.getValue().getAsString()); + // Get compound fields + JsonArray jTags = jObj.getAsJsonArray("filter_tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } } @@ -130,6 +136,7 @@ public void loadJson(File jsonFile) throws IOException { } } + /** * Loads Report config information from a config json string @@ -146,30 +153,37 @@ public void loadJsonString(List confJson) throws JsonParseException { JsonElement jElement = jsonParser.parse(confJson.get(0)); JsonObject jObj = jElement.getAsJsonObject(); // Get the simple fields - this.id = jObj.getAsJsonPrimitive("id").getAsString(); - this.tenant = jObj.getAsJsonPrimitive("tenant").getAsString(); - this.report = jObj.getAsJsonPrimitive("job").getAsString(); - this.egroup = jObj.getAsJsonPrimitive("egroup").getAsString(); - this.ggroup = jObj.getAsJsonPrimitive("ggroup").getAsString(); - this.weight = jObj.getAsJsonPrimitive("weight").getAsString(); - this.agroup = jObj.getAsJsonPrimitive("altg").getAsString(); - // Get compound fields - JsonObject jEgroupTags = jObj.getAsJsonObject("egroup_tags"); - JsonObject jGgroupTags = jObj.getAsJsonObject("ggroup_tags"); - JsonObject jMdataTags = jObj.getAsJsonObject("mdata_tags"); - - // Iterate fields - for (Entry item : jEgroupTags.entrySet()) { - - this.egroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jGgroupTags.entrySet()) { - - this.ggroupTags.put(item.getKey(), item.getValue().getAsString()); + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + // optional weight filtering + this.weight = ""; + if (jObj.has("weight")){ + this.weight = jObj.get("weight").getAsString(); } - for (Entry item : jMdataTags.entrySet()) { - - this.mdataTags.put(item.getKey(), item.getValue().getAsString()); + // Get compound fields + JsonArray jTags = jObj.getAsJsonArray("tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } } } catch (JsonParseException ex) { diff --git a/flink_jobs/batch_status/src/main/java/ops/ThresholdManager.java b/flink_jobs/batch_status/src/main/java/ops/ThresholdManager.java new file mode 100644 index 00000000..e69a0190 --- /dev/null +++ b/flink_jobs/batch_status/src/main/java/ops/ThresholdManager.java @@ -0,0 +1,754 @@ +package ops; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; + +/** + * @author kaggis + * + */ +public class ThresholdManager { + + private static final Logger LOG = Logger.getLogger(ThresholdManager.class.getName()); + + // Nested map that holds rule definitions: "groups/hosts/metrics" -> label -> + // threshold + // rules" + private Map> rules; + + // Reverse index checks for group, host, metrics + private HashSet metrics; + private HashSet hosts; + private HashSet groups; + private String aggregationOp = "AND"; + + public Map> getRules() { + return this.rules; + } + + /** + * Threshold class implements objects that hold threshold values as they are + * parsed by a threshold expression such as the following one: + * + * label=30s;0:50,50:100,0,100 + * + * A Threshold object can be directly constructed from a string including an + * expression as the above + * + * Each threshold object stores the threshold expression and the individual + * parsed items such as value, uom, warning range, critical range and min,max + * values + * + */ + class Threshold { + + private static final String defWarning = "WARNING"; + private static final String defCritical = "CRITICAL"; + + private String expression; + private String label; + private Float value; + private String uom; + private Range warning; + private Range critical; + private Float min; + private Float max; + + + + /** + * Constructs a threshold from a string containing a threshold expression + * + * @param expression + * A string containing a threshold exception as the following one: + * label=30s;0:50,50:100,0,100 + * + */ + public Threshold(String expression) { + Threshold temp = parseAndSet(expression); + this.expression = temp.expression; + this.label = temp.label; + this.value = temp.value; + this.uom = temp.uom; + this.warning = temp.warning; + this.critical = temp.critical; + this.min = temp.min; + this.max = temp.max; + + } + + /** + * Create a new threshold object by providing each parameter + * + * @param expression + * string containing the threshold expression + * @param label + * threshold label + * @param value + * threshold value + * @param uom + * unit of measurement - optional + * @param warning + * a range determining warning statuses + * @param critical + * a range determining critical statuses + * @param min + * minimum value available for this threshold + * @param max + * maximum value available for this threshold + */ + public Threshold(String expression, String label, float value, String uom, Range warning, Range critical, + float min, float max) { + + this.expression = expression; + this.label = label; + this.value = value; + this.uom = uom; + this.warning = warning; + this.critical = critical; + this.min = min; + this.max = max; + } + + public String getExpression() { + return expression; + } + + public String getLabel() { + return label; + } + + public float getValue() { + return value; + } + + public String getUom() { + return uom; + } + + public Range getWarning() { + return warning; + } + + public Range getCritical() { + return critical; + } + + public float getMin() { + return min; + } + + public float getMax() { + return max; + } + + /** + * Parses a threshold expression string and returns a Threshold object + * + * @param threshold + * string containing the threshold expression + * @return Threshold object + */ + public Threshold parseAndSet(String threshold) { + + String pThresh = threshold; + String curLabel = ""; + String curUom = ""; + Float curValue = Float.NaN; + Range curWarning = new Range(); // empty range + Range curCritical = new Range(); // emtpy range + Float curMin = Float.NaN; + Float curMax = Float.NaN; + // find label by splitting at = + String[] tokens = pThresh.split("="); + // Must have two tokens to continue, label=something + if (tokens.length == 2) { + curLabel = tokens[0]; + + // Split right value by ; to find the array of arguments + String[] subtokens = tokens[1].split(";"); + // Must have size > 0 at least a value + if (subtokens.length > 0) { + curUom = getUOM(subtokens[0]); + curValue = Float.parseFloat(subtokens[0].replaceAll(curUom, "")); + if (subtokens.length > 1) { + // iterate over rest of subtokens + for (int i = 1; i < subtokens.length; i++) { + if (i == 1) { + // parse warning range + curWarning = new Range(subtokens[i]); + continue; + } else if (i == 2) { + // parse critical + curCritical = new Range(subtokens[i]); + continue; + } else if (i == 3) { + // parse min + curMin = Float.parseFloat(subtokens[i]); + continue; + } else if (i == 4) { + // parse min + curMax = Float.parseFloat(subtokens[i]); + } + } + } + + } + + } + + return new Threshold(threshold, curLabel, curValue, curUom, curWarning, curCritical, curMin, curMax); + + } + + /** + * Reads a threshold string value and extracts the unit of measurement if + * present + * + * @param value + * String containing a representation of the value and uom + * @return String representing the uom. + */ + public String getUOM(String value) { + // check if ends with digit + if (Character.isDigit(value.charAt(value.length() - 1))) { + return ""; + } + + // check if ends with seconds + if (value.endsWith("s")) + return "s"; + if (value.endsWith("us")) + return "us"; + if (value.endsWith("ms")) + return "ms"; + if (value.endsWith("%")) + return "%"; + if (value.endsWith("B")) + return "B"; + if (value.endsWith("KB")) + return "KB"; + if (value.endsWith("MB")) + return "MB"; + if (value.endsWith("TB")) + return "TB"; + if (value.endsWith("c")) + return "c"; + + // Not valid range + throw new RuntimeException("Invalid Unit of measurement: " + value); + + } + + /** + * Checks an external value against a threshold's warning,critical ranges. If a + * range contains the value (warning or critical) the corresponding status is + * returned as string "WARNING" or "CRITICAL". If the threshold doesn't provide + * the needed data to decide on status an "" is returned back. + * + * @return string with the status result "WARNING", "CRITICAL" + */ + public String calcStatusWithValue(Float value) { + + if (!Float.isFinite(this.value)) + return ""; + if (!this.warning.isUndef()) { + if (this.warning.contains(value)) + return defWarning; + } + if (!this.critical.isUndef()) { + if (this.critical.contains(value)) + return defCritical; + } + + return ""; + } + + /** + * Checks a threshold's value against warning,critical ranges. If a range + * contains the value (warning or critical) the corresponding status is returned + * as string "WARNING" or "CRITICAL". If the threshold doesn't provide the + * needed data to decide on status an "" is returned back. + * + * @return string with the status result "WARNING", "CRITICAL" + */ + public String calcStatus() { + + if (!Float.isFinite(this.value)) + return ""; + if (!this.warning.isUndef()) { + if (this.warning.contains(this.value)) + return defWarning; + } + if (!this.critical.isUndef()) { + if (this.critical.contains(this.value)) + return defCritical; + } + + return ""; + } + + public String toString() { + String strWarn = ""; + String strCrit = ""; + String strMin = ""; + String strMax = ""; + + if (this.warning != null) + strWarn = this.warning.toString(); + if (this.critical != null) + strCrit = this.critical.toString(); + if (this.min != null) + strMin = this.min.toString(); + if (this.max != null) + strMax = this.max.toString(); + + return "[expression=" + this.expression + ", label=" + this.label + ", value=" + this.value + ", uom=" + + this.uom + ", warning=" + strWarn + ", critical=" + strCrit + ", min=" + strMin + ", max=" + + strMax + ")"; + } + + } + + /** + * Range implements a simple object that holds a threshold's critical or warning + * range. It includes a floor,ceil as floats and an exclude flag when a range is + * supposed to be used for exclusion and not inclusion. The threshold spec uses + * an '@' character in front of a range to define inversion(exclusion) + * + * Inclusion assumes that floor < value < ceil and not floor <= value <= ceil + * + */ + class Range { + Float floor; + Float ceil; + Boolean exclude; + + /** + * Creates an empty range. Invert is false and limits are NaN + */ + public Range() { + this.floor = Float.NaN; + this.ceil = Float.NaN; + this.exclude = false; + } + + /** + * Creates a range by parameters + * + * @param floor + * Float that defines the lower limit of the range + * @param ceil + * Float that defines the upper limit of the range + * @param exclude + * boolean that defines if the range is used for inclusion (true) or + * exlusion (false) + */ + public Range(Float floor, Float ceil, Boolean exclude) { + this.floor = floor; + this.ceil = ceil; + this.exclude = exclude; + } + + /** + * Creates a range by parsing a range expression string like the following one: + * '0:10' + * + * @param range + * string including a range expression + */ + public Range(String range) { + Range tmp = parseAndSet(range); + this.floor = tmp.floor; + this.ceil = tmp.ceil; + this.exclude = tmp.exclude; + } + + /** + * Checks if a Range is undefined (float,ceiling are NaN) + * + * @return boolean + */ + public boolean isUndef() { + return this.floor == Float.NaN || this.ceil == Float.NaN; + } + + /** + * Checks if a value is included in range (or truly excluded if range is an + * exclusion) + * + * @param value + * Float + * @return boolean + */ + public boolean contains(Float value) { + boolean result = value > this.floor && value < this.ceil; + if (this.exclude) { + return !result; + } + return result; + } + + /** + * Parses a range expression string and creates a Range object Range expressions + * can be in the following forms: + *

    + *
  • 10 - range starting from 0 to 10
  • + *
  • 10: - range starting from 10 to infinity
  • + *
  • ~:20 - range starting from negative inf. up to 20
  • + *
  • 20:30 - range between two numbers
  • + *
  • @20:30 - inverted range, excludes betweeen two numbers + *
+ * + * @param expression + * String containing a range expression + * @return + */ + public Range parseAndSet(String expression) { + String parsedRange = expression; + Float curFloor = 0F; + Float curCeil = 0F; + boolean curInv = false; + if (parsedRange.replaceAll(" ", "").equals("")) { + return new Range(); + } + // check if invert + if (parsedRange.startsWith("@")) { + curInv = true; + // after check remove @ from range string + parsedRange = parsedRange.replaceAll("^@", ""); + } + + // check if range string doesn't have separator : + if (!parsedRange.contains(":")) { + // then we are in the case of a single number like 10 + // which defines the rule 0 --> 10 so + curFloor = 0F; + curCeil = Float.parseFloat(parsedRange); + + return new Range(curFloor, curCeil, curInv); + } + + // check if range end with separator : + if (parsedRange.endsWith(":")) { + parsedRange = parsedRange.replaceAll(":$", ""); + // then we are in the case of a signle number like 10: + // which defines the rule 10 --> positive infinity + curFloor = Float.parseFloat(parsedRange); + curCeil = Float.POSITIVE_INFINITY; + return new Range(curFloor, curCeil, curInv); + } + + // tokenize string without prefixes + String[] tokens = parsedRange.split(":"); + if (tokens.length == 2) { + // check if token[0] is negative infinity ~ + if (tokens[0].equalsIgnoreCase("~")) { + curFloor = Float.NEGATIVE_INFINITY; + } else { + curFloor = Float.parseFloat(tokens[0]); + } + + curCeil = Float.parseFloat(tokens[1]); + return new Range(curFloor, curCeil, curInv); + } + + // Not valid range + throw new RuntimeException("Invalid threshold: " + expression); + + } + + public String toString() { + return "(floor=" + this.floor + ",ceil=" + this.ceil + ",invert=" + this.exclude.toString() + ")"; + } + + } + + /** + * Creates a Manager that parses rules files with thresholds and stores them + * internally as objects. A ThresholdManager can be used to automatically + * calculate statuses about a monitoring item (group,host,metric) based on the + * most relevant threshold rules stored in it. + */ + public ThresholdManager() { + + this.rules = new HashMap>(); + this.hosts = new HashSet(); + this.groups = new HashSet(); + this.metrics = new HashSet(); + + } + + /** + * Return the default operation when aggregating statuses generated from multiple threshold rules + * @return + */ + public String getAggregationOp() { + return this.aggregationOp; + } + + + /** + * @param op string with the name of the operation to be used in the aggregation (AND,OR,custom one) + */ + public void setAggregationOp(String op) { + this.aggregationOp = op; + } + + /** + * Returns a status calculation for a specific rule key Each rule key is defined + * as follows: 'group/host/metric' and leads to a threshold rule. Group and host + * parts are optional as such: 'group//metric' or '/host/metric' or '//metric' + * + * @param rule + * string containing a rule key + * @param opsMgr + * an OpsManager Object to handle status aggregations + * @param opType + * an OpsManager operation to be used (like 'OR', 'AND') + * @return string with status result + */ + public String getStatusByRule(String rule, OpsManager opsMgr, String opType) { + + if (!rules.containsKey(rule)) + return ""; + String status = ""; + Map tholds = rules.get(rule); + for (Entry thold : tholds.entrySet()) { + // first step + if (status == "") { + status = thold.getValue().calcStatus(); + continue; + } + String statusNext = thold.getValue().calcStatus(); + if (statusNext != "") { + status = opsMgr.op(opType, status, statusNext); + } + } + return status; + } + + /** + * Returns a status calculation for a specific rule key Each rule key is defined + * as follows: 'group/host/metric' and leads to a threshold rule. Group and host + * parts are optional as such: 'group//metric' or '/host/metric' or '//metric' + * + * @param rule + * string containing a rule key + * @param opsMgr + * an OpsManager Object to handle status aggregations + * @param opType + * an OpsManager operation to be used (like 'OR', 'AND') + * @return string array with two elements. First element is the status result and second one the rule applied + */ + public String[] getStatusByRuleAndValues(String rule, OpsManager opsMgr, String opType, Map values) { + + if (!rules.containsKey(rule)) + return new String[] {"",""}; + String status = ""; + String explain = ""; + Map tholds = rules.get(rule); + + for ( Entry value : values.entrySet()) { + String label = value.getKey(); + if (tholds.containsKey(label)) { + Threshold th = tholds.get(label); + // first step + if (status == "") { + + status = th.calcStatusWithValue(value.getValue()); + explain = th.getExpression(); + continue; + } + + String statusNext = th.calcStatusWithValue(value.getValue()); + + if (statusNext != "") { + status = opsMgr.op(opType, status, statusNext); + explain = explain + " " + th.getExpression(); + } + } + } + + + return new String[]{status,explain}; + + } + + /** + * Gets the most relevant rule based on a monitoring item (group,host,metric) + * using the following precedence (specific to least specific) (group, host, + * metric) #1 ( , host, metric) #2 (group, , metric) #3 ( , , metric) #4 + * + * @param group + * string with name of the monitored endpoint group + * @param host + * string with name of the monitored host + * @param metric + * string with name of the monitored metric + * @return a string with the relevant rule key + */ + public String getMostRelevantRule(String group, String host, String metric) { + if (!this.metrics.contains(metric)) { + return ""; // nothing found + } else { + + // order or precedence: more specific first + // group,host,metric #1 + // ,host,metric #2 + // group ,metric #3 + // ,metric #4 + if (this.hosts.contains(host)) { + if (this.groups.contains(group)) { + // check if combined entry indeed exists + String key = String.format("%s/%s/%s", group, host, metric); + if (this.rules.containsKey(key)) + return key; // #1 + + } else { + return String.format("/%s/%s", host, metric); // #2 + } + } + + if (this.groups.contains(group)) { + // check if combined entry indeed exists + String key = String.format("%s//%s", group, metric); // #3 + if (this.rules.containsKey(key)) + return key; + } + + return String.format("//%s", metric); + } + + } + + /** + * Parses an expression that might contain multiple labels=thresholds separated + * by whitespace and creates a HashMap of labels to parsed threshold objects + * + * @param thresholds + * an expression that might contain multiple thresholds + * @return a HashMap to Threshold objects + */ + public Map parseThresholds(String thresholds) { + Map subMap = new HashMap(); + // Tokenize with lookahead on the point when a new label starts + String[] tokens = thresholds.split("(;|[ ]+)(?=[a-zA-Z])"); + for (String token : tokens) { + Threshold curTh = new Threshold(token); + if (curTh != null) { + subMap.put(curTh.getLabel(), curTh); + } + } + return subMap; + } + + /** + * Parses an expression that might contain multiple labels=thresholds separated + * by whitespace and creates a HashMap of labels to parsed Float values + * + * @param thresholds + * an expression that might contain multiple thresholds + * @return a HashMap to Floats + */ + public Map getThresholdValues(String thresholds) { + Map subMap = new HashMap(); + // tokenize thresholds by whitespace + String[] tokens = thresholds.split("(;|[ ]+)(?=[a-zA-Z])"); + for (String token : tokens) { + Threshold curTh = new Threshold(token); + if (curTh != null) { + subMap.put(curTh.getLabel(), curTh.getValue()); + } + } + return subMap; + } + + /** + * Parses a JSON threshold rule file and populates the ThresholdManager + * + * @param jsonFile + * File to be parsed + * @return boolean signaling whether operation succeeded or not + */ + public boolean parseJSONFile(File jsonFile) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(jsonFile)); + String jsonStr = IOUtils.toString(br); + if (!parseJSON(jsonStr)) + return false; + + } catch (IOException ex) { + LOG.error("Could not open file:" + jsonFile.getName()); + return false; + + } catch (JsonParseException ex) { + LOG.error("File is not valid json:" + jsonFile.getName()); + return false; + } finally { + // Close quietly without exceptions the buffered reader + IOUtils.closeQuietly(br); + } + + return true; + + } + + /** + * Parses a json string with the appropriate threshold rule schema and populates + * the ThresholdManager + * + * @param jsonString + * string containing threshold rules in json format + * @return boolean signaling whether the parse information succeded or not + */ + public boolean parseJSON(String jsonString) { + + + JsonParser json_parser = new JsonParser(); + JsonObject jRoot = json_parser.parse(jsonString).getAsJsonObject(); + JsonArray jRules = jRoot.getAsJsonArray("rules"); + for (JsonElement jRule : jRules) { + JsonObject jRuleObj = jRule.getAsJsonObject(); + String ruleMetric = jRuleObj.getAsJsonPrimitive("metric").getAsString(); + String ruleHost = ""; + String ruleEgroup = ""; + + if (jRuleObj.has("host")) { + ruleHost = jRuleObj.getAsJsonPrimitive("host").getAsString(); + } + if (jRuleObj.has("endpoint_group")) { + ruleEgroup = jRuleObj.getAsJsonPrimitive("endpoint_group").getAsString(); + } + + String ruleThr = jRuleObj.getAsJsonPrimitive("thresholds").getAsString(); + this.metrics.add(ruleMetric); + if (ruleHost != "") + this.hosts.add(ruleHost); + if (ruleEgroup != "") + this.groups.add(ruleEgroup); + String full = ruleEgroup + "/" + ruleHost + "/" + ruleMetric; + Map thrMap = parseThresholds(ruleThr); + this.rules.put(full, thrMap); + } + + return true; + } + +} diff --git a/flink_jobs/batch_status/src/main/java/sync/AggregationProfileManager.java b/flink_jobs/batch_status/src/main/java/sync/AggregationProfileManager.java index 6c526407..11648956 100644 --- a/flink_jobs/batch_status/src/main/java/sync/AggregationProfileManager.java +++ b/flink_jobs/batch_status/src/main/java/sync/AggregationProfileManager.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map.Entry; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; @@ -235,28 +236,31 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jRootElement = jsonParser.parse(br); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } @@ -289,28 +293,32 @@ public void loadJsonString(List apsJson) throws IOException { JsonElement jRootElement = jsonParser.parse(apsJson.get(0)); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); + tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } diff --git a/flink_jobs/batch_status/src/main/resources/ops/EGI-algorithm.json b/flink_jobs/batch_status/src/main/resources/ops/EGI-algorithm.json new file mode 100644 index 00000000..b88d8c99 --- /dev/null +++ b/flink_jobs/batch_status/src/main/resources/ops/EGI-algorithm.json @@ -0,0 +1,239 @@ +{ + "id": "1b0318f0-429d-44fc-8bba-07184354c73b", + "name": "egi_ops", + "available_states": [ + "OK", + "WARNING", + "UNKNOWN", + "MISSING", + "CRITICAL", + "DOWNTIME" + ], + "defaults": { + "down": "DOWNTIME", + "missing": "MISSING", + "unknown": "UNKNOWN" + }, + "operations": [ + { + "name": "AND", + "truth_table": [ + { + "a": "OK", + "b": "OK", + "x": "OK" + }, + { + "a": "OK", + "b": "WARNING", + "x": "WARNING" + }, + { + "a": "OK", + "b": "UNKNOWN", + "x": "UNKNOWN" + }, + { + "a": "OK", + "b": "MISSING", + "x": "MISSING" + }, + { + "a": "OK", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "OK", + "b": "DOWNTIME", + "x": "DOWNTIME" + }, + { + "a": "WARNING", + "b": "WARNING", + "x": "WARNING" + }, + { + "a": "WARNING", + "b": "UNKNOWN", + "x": "UNKNOWN" + }, + { + "a": "WARNING", + "b": "MISSING", + "x": "MISSING" + }, + { + "a": "WARNING", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "WARNING", + "b": "DOWNTIME", + "x": "DOWNTIME" + }, + { + "a": "UNKNOWN", + "b": "UNKNOWN", + "x": "UNKNOWN" + }, + { + "a": "UNKNOWN", + "b": "MISSING", + "x": "MISSING" + }, + { + "a": "UNKNOWN", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "UNKNOWN", + "b": "DOWNTIME", + "x": "DOWNTIME" + }, + { + "a": "MISSING", + "b": "MISSING", + "x": "MISSING" + }, + { + "a": "MISSING", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "MISSING", + "b": "DOWNTIME", + "x": "DOWNTIME" + }, + { + "a": "CRITICAL", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "CRITICAL", + "b": "DOWNTIME", + "x": "CRITICAL" + }, + { + "a": "DOWNTIME", + "b": "DOWNTIME", + "x": "DOWNTIME" + } + ] + }, + { + "name": "OR", + "truth_table": [ + { + "a": "OK", + "b": "OK", + "x": "OK" + }, + { + "a": "OK", + "b": "WARNING", + "x": "OK" + }, + { + "a": "OK", + "b": "UNKNOWN", + "x": "OK" + }, + { + "a": "OK", + "b": "MISSING", + "x": "OK" + }, + { + "a": "OK", + "b": "CRITICAL", + "x": "OK" + }, + { + "a": "OK", + "b": "DOWNTIME", + "x": "OK" + }, + { + "a": "WARNING", + "b": "WARNING", + "x": "WARNING" + }, + { + "a": "WARNING", + "b": "UNKNOWN", + "x": "WARNING" + }, + { + "a": "WARNING", + "b": "MISSING", + "x": "WARNING" + }, + { + "a": "WARNING", + "b": "CRITICAL", + "x": "WARNING" + }, + { + "a": "WARNING", + "b": "DOWNTIME", + "x": "WARNING" + }, + { + "a": "UNKNOWN", + "b": "UNKNOWN", + "x": "UNKNOWN" + }, + { + "a": "UNKNOWN", + "b": "MISSING", + "x": "UNKNOWN" + }, + { + "a": "UNKNOWN", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "UNKNOWN", + "b": "DOWNTIME", + "x": "UNKNOWN" + }, + { + "a": "MISSING", + "b": "MISSING", + "x": "MISSING" + }, + { + "a": "MISSING", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "MISSING", + "b": "DOWNTIME", + "x": "DOWNTIME" + }, + { + "a": "CRITICAL", + "b": "CRITICAL", + "x": "CRITICAL" + }, + { + "a": "CRITICAL", + "b": "DOWNTIME", + "x": "CRITICAL" + }, + { + "a": "DOWNTIME", + "b": "DOWNTIME", + "x": "DOWNTIME" + } + ] + } + ] +} diff --git a/flink_jobs/batch_status/src/main/resources/ops/EGI-rules.json b/flink_jobs/batch_status/src/main/resources/ops/EGI-rules.json new file mode 100644 index 00000000..2a52c99a --- /dev/null +++ b/flink_jobs/batch_status/src/main/resources/ops/EGI-rules.json @@ -0,0 +1,33 @@ +{ + "rules": [ + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30" + }, + { + "metric": "org.bdii.Entries", + "thresholds": "time=-35s;~:10;15:;-100;300 entries=55;20;50:60;50;30" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s; entries=29;;30:50", + "host" : "bdii.host3.example.foo" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=29;0:10;20:30;0;30", + "host" : "bdii.host1.example.foo" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30", + "host" : "bdii.host1.example.foo", + "endpoint_group": "SITE-101" + }, + { + "metric": "org.bdii.Freshness", + "thresholds": "freshness=10s;30;50:60;0;100 entries=5;0:10;20:30;50;30", + "endpoint_group": "SITE-101" + } + ] +} diff --git a/flink_jobs/batch_status/src/test/java/ops/ThresholdManagerTest.java b/flink_jobs/batch_status/src/test/java/ops/ThresholdManagerTest.java new file mode 100644 index 00000000..b2250b33 --- /dev/null +++ b/flink_jobs/batch_status/src/test/java/ops/ThresholdManagerTest.java @@ -0,0 +1,91 @@ +package ops; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.junit.BeforeClass; +import org.junit.Test; + +public class ThresholdManagerTest { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // Assert that files are present + assertNotNull("Test file missing", ThresholdManagerTest.class.getResource("/ops/EGI-algorithm.json")); + assertNotNull("Test file missing", ThresholdManagerTest.class.getResource("/ops/EGI-rules.json")); + } + + @Test + public void test() throws IOException, URISyntaxException { + + // Prepare Resource File + URL opsJsonFile = ThresholdManagerTest.class.getResource("/ops/EGI-algorithm.json"); + File opsFile = new File(opsJsonFile.toURI()); + // Instantiate class + OpsManager opsMgr = new OpsManager(); + // Test loading file + opsMgr.loadJson(opsFile); + + // Prepare Resource File + URL thrJsonFile = ThresholdManagerTest.class.getResource("/ops/EGI-rules.json"); + File thrFile = new File(thrJsonFile.toURI()); + // Instantiate class + ThresholdManager t = new ThresholdManager(); + t.parseJSONFile(thrFile); + + String[] expectedRules = new String[] { "//org.bdii.Freshness", "//org.bdii.Entries", + "/bdii.host1.example.foo/org.bdii.Freshness", "/bdii.host3.example.foo/org.bdii.Freshness", + "SITE-101/bdii.host1.example.foo/org.bdii.Freshness", "SITE-101//org.bdii.Freshness" }; + + assertEquals(expectedRules.length, t.getRules().entrySet().size()); + + for (String rule : expectedRules) { + assertEquals(true, t.getRules().keySet().contains(rule)); + } + + assertEquals("SITE-101/bdii.host1.example.foo/org.bdii.Freshness", + t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Freshness")); + + assertEquals("SITE-101//org.bdii.Freshness", + t.getMostRelevantRule("SITE-101", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Freshness", + t.getMostRelevantRule("SITE-202", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Freshness", + t.getMostRelevantRule("SITE-202", "bdii.host2.example.foo", "org.bdii.Freshness")); + + assertEquals("//org.bdii.Entries", + t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Entries")); + + assertEquals("", t.getMostRelevantRule("SITE-101", "bdii.host1.example.foo", "org.bdii.Foo")); + + assertEquals("WARNING", t.getStatusByRule("SITE-101/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "AND")); + assertEquals("CRITICAL", t.getStatusByRule("//org.bdii.Entries", opsMgr, "AND")); + assertEquals("WARNING", t.getStatusByRule("//org.bdii.Entries", opsMgr, "OR")); + assertEquals("CRITICAL", t.getStatusByRule("/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "AND")); + assertEquals("WARNING", t.getStatusByRule("/bdii.host1.example.foo/org.bdii.Freshness", opsMgr, "OR")); + assertEquals("",t.getStatusByRule("/bdii.host3.example.foo/org.bdii.Freshness", opsMgr, "AND")); //no critical or warning ranges defined + + // Test parsing of label=value lists including space separation or not + assertEquals("{size=6754.0, time=3.714648}",t.getThresholdValues("time=3.714648s;;;0.000000 size=6754B;;;0").toString()); + assertEquals("{time=0.037908}",t.getThresholdValues("time=0.037908s;;;0.000000;120.000000").toString()); + assertEquals("{time=0.041992}",t.getThresholdValues("time=0.041992s;;;0.000000;120.000000").toString()); + assertEquals("{entries=1.0, time=0.15}",t.getThresholdValues("time=0.15s;entries=1").toString()); + assertEquals("{entries=1.0, freshness=111.0}",t.getThresholdValues("freshness=111s;entries=1").toString()); + assertEquals("{entries=1.0, freshness=111.0}",t.getThresholdValues("freshness=111s; entries=1").toString()); + assertEquals("{entries=1.0, freshness=111.0}",t.getThresholdValues("freshness=111s;;;entries=1").toString()); + assertEquals("{entries=1.0, freshness=111.0}",t.getThresholdValues("freshness=111s;; entries=1").toString()); + assertEquals("{TSSInstances=1.0}",t.getThresholdValues("TSSInstances=1").toString()); + + String thBig = "tls_ciphers=105.47s dir_head=0.69s dir_get=0.89s file_put=0.82s file_get=0.45s file_options=0.39s file_move=0.42s file_head=0.40s file_head_on_non_existent=0.38s file_propfind=0.40s file_delete=0.72s file_delete_on_non_existent=0.37s"; + String expThBig = "{file_head_on_non_existent=0.38, file_put=0.82, file_delete_on_non_existent=0.37, file_delete=0.72, dir_head=0.69, file_head=0.4, file_propfind=0.4, dir_get=0.89, file_move=0.42, file_options=0.39, file_get=0.45, tls_ciphers=105.47}"; + + assertEquals(expThBig,t.getThresholdValues(thBig).toString()); + } + +} diff --git a/flink_jobs/stream_status/src/main/java/argo/streaming/AmsStreamStatus.java b/flink_jobs/stream_status/src/main/java/argo/streaming/AmsStreamStatus.java index be912615..69199169 100644 --- a/flink_jobs/stream_status/src/main/java/argo/streaming/AmsStreamStatus.java +++ b/flink_jobs/stream_status/src/main/java/argo/streaming/AmsStreamStatus.java @@ -64,6 +64,8 @@ * --sync.aps : availability profile used * --sync.ops : operations profile used * --sync.downtimes : initial downtime file (same for run date) + * --report : report name + * --report.uuid : report uuid * Job optional cli parameters: * --ams.batch : num of messages to be retrieved per request to AMS service * --ams.interval : interval (in ms) between AMS service requests @@ -160,6 +162,7 @@ public static void main(String[] args) throws Exception { String project = parameterTool.getRequired("ams.project"); String subMetric = parameterTool.getRequired("ams.sub.metric"); String subSync = parameterTool.getRequired("ams.sub.sync"); + // set ams client batch and interval to default values int batch = 1; @@ -206,7 +209,7 @@ public static void main(String[] args) throws Exception { DataStream events = groupMdata.connect(syncB).flatMap(new StatusMap(conf)); - events.print(); + if (hasKafkaArgs(parameterTool)) { // Initialize kafka parameters @@ -236,7 +239,7 @@ public static void main(String[] args) throws Exception { MongoStatusOutput mongoOut = new MongoStatusOutput(parameterTool.get("mongo.uri"), "status_metrics", "status_endpoints", "status_services", "status_endpoint_groups", parameterTool.get("mongo.method"), - parameterTool.get("report")); + parameterTool.get("report.uuid")); events.writeUsingOutputFormat(mongoOut); } @@ -421,6 +424,8 @@ private static class StatusMap extends RichCoFlatMapFunction downList = sd.readDowntime(config.downtime); @@ -451,6 +456,7 @@ public void open(Configuration parameters) throws IOException, ParseException, U // create a new status manager sm = new StatusManager(); sm.setTimeout(config.timeout); + sm.setReport(config.report); // load all the connector data sm.loadAll(config.runDate, downList, egpListFull, mpsList, apsJSON, opsJSON); @@ -524,10 +530,13 @@ public void flatMap2(String value, Collector out) throws IOException, Pa // Decode from base64 byte[] decoded64 = Base64.decodeBase64(data.getBytes("UTF-8")); JsonElement jAttr = jRoot.getAsJsonObject().get("attributes"); + Map attr = SyncParse.parseAttributes(jAttr); - if (attr.containsKey("type")) { - + // The sync dataset should have a type and report attribute and report should be the job's report + if (attr.containsKey("type") && attr.containsKey("report") && attr.get("report") == config.report ) { + String sType = attr.get("type"); + LOG.info("Accepted " + sType + " for report: " + attr.get("report")); if (sType.equalsIgnoreCase("metric_profile")) { // Update mps ArrayList mpsList = SyncParse.parseMetricProfile(decoded64); @@ -547,7 +556,6 @@ public void flatMap2(String value, Collector out) throws IOException, Pa egpTrim.add(egpItem); } } - sm.egp = new EndpointGroupManagerV2(); sm.egp.loadFromList(egpTrim); } else if (sType.equals("downtimes") && attr.containsKey("partition_date")) { @@ -556,6 +564,8 @@ public void flatMap2(String value, Collector out) throws IOException, Pa // Update downtime cache in status manager sm.addDowntimeSet(pDate, downList); } + } else { + LOG.info("Declined " + attr.get("type") + "for report: " + attr.get("report")); } } diff --git a/flink_jobs/stream_status/src/main/java/argo/streaming/ArgoMessagingClient.java b/flink_jobs/stream_status/src/main/java/argo/streaming/ArgoMessagingClient.java index dcf1d2b5..4e6e1527 100644 --- a/flink_jobs/stream_status/src/main/java/argo/streaming/ArgoMessagingClient.java +++ b/flink_jobs/stream_status/src/main/java/argo/streaming/ArgoMessagingClient.java @@ -32,7 +32,8 @@ import org.apache.http.client.methods.CloseableHttpResponse; /** - * Simple http client for pulling and acknowledging messages from AMS service http API + * Simple http client for pulling and acknowledging messages from AMS service + * http API */ public class ArgoMessagingClient { @@ -53,9 +54,9 @@ public class ArgoMessagingClient { private String maxMessages = ""; // ssl verify or not private boolean verify = true; - // proxy + // proxy private URI proxy = null; - + // Utility inner class for holding list of messages and acknowledgements private class MsgAck { String[] msgs; @@ -78,8 +79,9 @@ public ArgoMessagingClient() { this.maxMessages = "100"; this.proxy = null; } - - public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + + public ArgoMessagingClient(String method, String token, String endpoint, String project, String sub, int batch, + boolean verify) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { this.proto = method; this.token = token; @@ -88,36 +90,36 @@ public ArgoMessagingClient(String method, String token, String endpoint, String this.sub = sub; this.maxMessages = String.valueOf(batch); this.verify = verify; - + this.httpClient = buildHttpClient(); - + } - + /** * Initializes Http Client (if not initialized during constructor) - * @return + * + * @return */ - private CloseableHttpClient buildHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private CloseableHttpClient buildHttpClient() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (this.verify) { return this.httpClient = HttpClients.createDefault(); } else { return this.httpClient = HttpClients.custom().setSSLSocketFactory(selfSignedSSLF()).build(); } } - + /** - * Create an SSL Connection Socket Factory with a strategy to trust self signed certificates + * Create an SSL Connection Socket Factory with a strategy to trust self signed + * certificates */ - private SSLConnectionSocketFactory selfSignedSSLF() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + private SSLConnectionSocketFactory selfSignedSSLF() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContextBuilder sslBuild = new SSLContextBuilder(); - sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); - - return new SSLConnectionSocketFactory(sslBuild.build(),NoopHostnameVerifier.INSTANCE); - - + sslBuild.loadTrustMaterial(null, new TrustSelfSignedStrategy()); + return new SSLConnectionSocketFactory(sslBuild.build(), NoopHostnameVerifier.INSTANCE); } - - + /** * Set AMS http client to use http proxy */ @@ -125,26 +127,37 @@ public void setProxy(String proxyURL) throws URISyntaxException { // parse proxy url this.proxy = URI.create(proxyURL); } - + /** * Set AMS http client to NOT use an http proxy */ public void unsetProxy() { - this.proxy=null; + this.proxy = null; } - - /** * Create a configuration for using http proxy on each request */ private RequestConfig createProxyCfg() { - HttpHost proxy = new HttpHost(this.proxy.getHost(),this.proxy.getPort(),this.proxy.getScheme()); + HttpHost proxy = new HttpHost(this.proxy.getHost(), this.proxy.getPort(), this.proxy.getScheme()); RequestConfig config = RequestConfig.custom().setProxy(proxy).build(); return config; } + public void logIssue(CloseableHttpResponse resp) throws UnsupportedOperationException, IOException { + InputStreamReader isRdr = new InputStreamReader(resp.getEntity().getContent()); + BufferedReader bRdr = new BufferedReader(isRdr); + int statusCode = resp.getStatusLine().getStatusCode(); + // Parse error content from api response + StringBuilder result = new StringBuilder(); + String rLine; + while ((rLine = bRdr.readLine()) != null) + result.append(rLine); + isRdr.close(); + Log.warn("ApiStatusCode={}, ApiErrorMessage={}", statusCode, result); + + } /** * Properly compose url for each AMS request @@ -173,11 +186,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit this.httpClient = buildHttpClient(); } - // check for proxy + // check for proxy if (this.proxy != null) { postPull.setConfig(createProxyCfg()); } - + CloseableHttpResponse response = this.httpClient.execute(postPull); String msg = ""; String ackId = ""; @@ -185,7 +198,9 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit HttpEntity entity = response.getEntity(); - if (entity != null) { + int statusCode = response.getStatusLine().getStatusCode(); + + if (entity != null && statusCode == 200) { InputStreamReader isRdr = new InputStreamReader(entity.getContent()); BufferedReader bRdr = new BufferedReader(isRdr); @@ -198,13 +213,11 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit // Gather message from json JsonParser jsonParser = new JsonParser(); // parse the json root object - + Log.info("response: {}", result.toString()); JsonElement jRoot = jsonParser.parse(result.toString()); JsonArray jRec = jRoot.getAsJsonObject().get("receivedMessages").getAsJsonArray(); - - // if has elements for (JsonElement jMsgItem : jRec) { JsonElement jMsg = jMsgItem.getAsJsonObject().get("message"); @@ -214,9 +227,13 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit msgList.add(msg); ackIdList.add(ackId); } - + isRdr.close(); + } else { + + logIssue(response); + } response.close(); @@ -224,8 +241,6 @@ public MsgAck doPull() throws IOException, KeyManagementException, NoSuchAlgorit String[] msgArr = msgList.toArray(new String[0]); String[] ackIdArr = ackIdList.toArray(new String[0]); - - // Return a Message array return new MsgAck(msgArr, ackIdArr); @@ -260,7 +275,6 @@ public String[] consume() throws KeyManagementException, NoSuchAlgorithmExceptio } catch (IOException e) { LOG.error(e.getMessage()); } - return msgs; } @@ -275,8 +289,8 @@ public String doAck(String ackId) throws IOException { StringEntity postBody = new StringEntity("{\"ackIds\":[" + ackId + "]}"); postBody.setContentType("application/json"); postAck.setEntity(postBody); - - // check for proxy + + // check for proxy if (this.proxy != null) { postAck.setConfig(createProxyCfg()); } @@ -301,10 +315,11 @@ public String doAck(String ackId) throws IOException { resMsg = result.toString(); isRdr.close(); + } else { + // Log any api errors + logIssue(response); } - response.close(); - // Return a resposeMessage return resMsg; diff --git a/flink_jobs/stream_status/src/main/java/argo/streaming/StatusConfig.java b/flink_jobs/stream_status/src/main/java/argo/streaming/StatusConfig.java index 94a673f9..9d423c4b 100644 --- a/flink_jobs/stream_status/src/main/java/argo/streaming/StatusConfig.java +++ b/flink_jobs/stream_status/src/main/java/argo/streaming/StatusConfig.java @@ -22,6 +22,8 @@ public class StatusConfig implements Serializable { // Avro schema public String avroSchema; + public String report; + // Sync files public String aps; public String mps; @@ -50,6 +52,7 @@ public StatusConfig(ParameterTool pt){ this.ops = pt.getRequired("sync.ops"); this.runDate = pt.getRequired("run.date"); this.downtime = pt.getRequired("sync.downtime"); + this.report = pt.getRequired("report"); // Optional timeout parameter if (pt.has("timeout")){ this.timeout = pt.getLong("timeout"); diff --git a/flink_jobs/stream_status/src/main/java/ops/ConfigManager.java b/flink_jobs/stream_status/src/main/java/ops/ConfigManager.java index 1853ecb3..511529fe 100644 --- a/flink_jobs/stream_status/src/main/java/ops/ConfigManager.java +++ b/flink_jobs/stream_status/src/main/java/ops/ConfigManager.java @@ -5,60 +5,57 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; - -import java.util.Map.Entry; +import java.util.List; import java.util.TreeMap; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; - - +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; + public class ConfigManager { private static final Logger LOG = Logger.getLogger(ConfigManager.class.getName()); public String id; // report uuid reference - public String tenant; public String report; + public String tenant; public String egroup; // endpoint group public String ggroup; // group of groups - public String agroup; // alternative group public String weight; // weight factor type public TreeMap egroupTags; public TreeMap ggroupTags; public TreeMap mdataTags; - public ConfigManager() { - this.tenant = null; this.report = null; this.id = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; this.egroupTags = new TreeMap(); this.ggroupTags = new TreeMap(); this.mdataTags = new TreeMap(); - + } public void clear() { - this.id=null; - this.tenant = null; + this.id = null; this.report = null; + this.tenant = null; this.egroup = null; this.ggroup = null; this.weight = null; this.egroupTags.clear(); this.ggroupTags.clear(); this.mdataTags.clear(); - + } public String getReportID() { @@ -73,12 +70,11 @@ public String getTenant() { return tenant; } + public String getEgroup() { return egroup; } - - public void loadJson(File jsonFile) throws IOException { // Clear data this.clear(); @@ -91,30 +87,35 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jElement = jsonParser.parse(br); JsonObject jObj = jElement.getAsJsonObject(); // Get the simple fields - this.id = jObj.getAsJsonPrimitive("id").getAsString(); - this.tenant = jObj.getAsJsonPrimitive("tenant").getAsString(); - this.report = jObj.getAsJsonPrimitive("job").getAsString(); - this.egroup = jObj.getAsJsonPrimitive("egroup").getAsString(); - this.ggroup = jObj.getAsJsonPrimitive("ggroup").getAsString(); - this.weight = jObj.getAsJsonPrimitive("weight").getAsString(); - this.agroup = jObj.getAsJsonPrimitive("altg").getAsString(); + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + + this.weight = jObj.get("weight").getAsString(); // Get compound fields - JsonObject jEgroupTags = jObj.getAsJsonObject("egroup_tags"); - JsonObject jGgroupTags = jObj.getAsJsonObject("ggroup_tags"); - JsonObject jMdataTags = jObj.getAsJsonObject("mdata_tags"); - JsonObject jDataMap = jObj.getAsJsonObject("datastore_maps"); - // Iterate fields - for (Entry item : jEgroupTags.entrySet()) { - - this.egroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jGgroupTags.entrySet()) { - - this.ggroupTags.put(item.getKey(), item.getValue().getAsString()); - } - for (Entry item : jMdataTags.entrySet()) { - - this.mdataTags.put(item.getKey(), item.getValue().getAsString()); + JsonArray jTags = jObj.getAsJsonArray("filter_tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } } @@ -132,4 +133,56 @@ public void loadJson(File jsonFile) throws IOException { } + + /** + * Loads Report config information from a config json string + * + */ + public void loadJsonString(List confJson) throws JsonParseException { + // Clear data + this.clear(); + + try { + + JsonParser jsonParser = new JsonParser(); + // Grab the first - and only line of json from ops data + JsonElement jElement = jsonParser.parse(confJson.get(0)); + JsonObject jObj = jElement.getAsJsonObject(); + // Get the simple fields + this.id = jObj.get("id").getAsString(); + this.tenant = jObj.get("tenant").getAsString(); + this.report = jObj.get("info").getAsJsonObject().get("name").getAsString(); + // get topology schema names + JsonObject topoGroup = jObj.get("topology_schema").getAsJsonObject().getAsJsonObject("group"); + this.ggroup = topoGroup.get("type").getAsString(); + this.egroup = topoGroup.get("group").getAsJsonObject().get("type").getAsString(); + this.weight = jObj.get("weight").getAsString(); + // Get compound fields + JsonArray jTags = jObj.getAsJsonArray("tags"); + + // Iterate tags + if (jTags != null) { + for (JsonElement tag : jTags) { + JsonObject jTag = tag.getAsJsonObject(); + String name = jTag.get("name").getAsString(); + String value = jTag.get("value").getAsString(); + String ctx = jTag.get("context").getAsString(); + if (ctx.equalsIgnoreCase("group_of_groups")){ + this.ggroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("endpoint_groups")){ + this.egroupTags.put(name, value); + } else if (ctx.equalsIgnoreCase("metric_data")) { + this.mdataTags.put(name, value); + } + + } + } + + } catch (JsonParseException ex) { + LOG.error("Not valid json contents"); + throw ex; + } + + } + } diff --git a/flink_jobs/stream_status/src/main/java/status/StatusManager.java b/flink_jobs/stream_status/src/main/java/status/StatusManager.java index a24992d8..a8fc5559 100644 --- a/flink_jobs/stream_status/src/main/java/status/StatusManager.java +++ b/flink_jobs/stream_status/src/main/java/status/StatusManager.java @@ -43,7 +43,7 @@ public class StatusManager { static Logger LOG = LoggerFactory.getLogger(StatusManager.class); // Name of the report used - String report; + private String report; // Sync file structures necessary for status computation public EndpointGroupManagerV2 egp = new EndpointGroupManagerV2(); @@ -70,6 +70,13 @@ public class StatusManager { // trigger String tsLatest; + public void setReport(String report) { + this.report = report; + } + + public String getReport() { + return this.report; + } public void setTimeout(Long timeout) { this.timeout = timeout; @@ -760,8 +767,9 @@ private String genEvent(String type, String group, String service, String hostna toZulu(ts), tsProc, prevStatus, toZulu(prevTs), new Boolean(repeat).toString(), summary, message ); Gson gson = new Gson(); - LOG.debug("Event Generated: " + gson.toJson(evnt)); - return gson.toJson(evnt); + String evntJson = gson.toJson(evnt); + LOG.debug("Event Generated: " + evntJson); + return evntJson; } diff --git a/flink_jobs/stream_status/src/main/java/sync/AggregationProfileManager.java b/flink_jobs/stream_status/src/main/java/sync/AggregationProfileManager.java index b43353dc..15a143c4 100644 --- a/flink_jobs/stream_status/src/main/java/sync/AggregationProfileManager.java +++ b/flink_jobs/stream_status/src/main/java/sync/AggregationProfileManager.java @@ -16,11 +16,14 @@ import java.util.List; import java.util.Map.Entry; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; + + public class AggregationProfileManager { private HashMap list; @@ -235,32 +238,36 @@ public void loadJson(File jsonFile) throws IOException { JsonElement jRootElement = jsonParser.parse(br); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); - // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); + tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } + // Add profile to the list this.list.put(tmpAvp.name, tmpAvp); @@ -289,32 +296,36 @@ public void loadJsonString(String apsJson) throws IOException { JsonElement jRootElement = jsonParser.parse(apsJson); JsonObject jRootObj = jRootElement.getAsJsonObject(); - JsonObject apGroups = jRootObj.getAsJsonObject("groups"); - // Create new entry for this availability profile AvProfileItem tmpAvp = new AvProfileItem(); + JsonArray apGroups = jRootObj.getAsJsonArray("groups"); + tmpAvp.name = jRootObj.get("name").getAsString(); tmpAvp.namespace = jRootObj.get("namespace").getAsString(); - tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsString(); - tmpAvp.metricOp = jRootObj.get("metric_ops").getAsString(); - tmpAvp.groupType = jRootObj.get("group_type").getAsString(); - tmpAvp.op = jRootObj.get("operation").getAsString(); + tmpAvp.metricProfile = jRootObj.get("metric_profile").getAsJsonObject().get("name").getAsString(); + tmpAvp.metricOp = jRootObj.get("metric_operation").getAsString(); + tmpAvp.groupType = jRootObj.get("endpoint_group").getAsString(); + tmpAvp.op = jRootObj.get("profile_operation").getAsString(); - for (Entry item : apGroups.entrySet()) { + for ( JsonElement item : apGroups) { // service name - String itemName = item.getKey(); - JsonObject itemObj = item.getValue().getAsJsonObject(); + JsonObject itemObj = item.getAsJsonObject(); + String itemName = itemObj.get("name").getAsString(); String itemOp = itemObj.get("operation").getAsString(); - JsonObject itemServices = itemObj.get("services").getAsJsonObject(); + JsonArray itemServices = itemObj.get("services").getAsJsonArray(); tmpAvp.insertGroup(itemName, itemOp); - for (Entry subItem : itemServices.entrySet()) { - tmpAvp.insertService(itemName, subItem.getKey(), subItem.getValue().getAsString()); + for (JsonElement subItem : itemServices) { + JsonObject subObj = subItem.getAsJsonObject(); + String serviceName = subObj.get("name").getAsString(); + String serviceOp = subObj.get("operation").getAsString(); + tmpAvp.insertService(itemName, serviceName,serviceOp); } } + // Add profile to the list this.list.put(tmpAvp.name, tmpAvp); diff --git a/flink_jobs/stream_status/src/main/resources/ops/ap1.json b/flink_jobs/stream_status/src/main/resources/ops/ap1.json index 4940b57a..d754320c 100644 --- a/flink_jobs/stream_status/src/main/resources/ops/ap1.json +++ b/flink_jobs/stream_status/src/main/resources/ops/ap1.json @@ -1,35 +1,64 @@ { - - "name": "ap1", - "namespace": "test", - "metric_profile": "ch.cern.sam.ROC_CRITICAL", - "metric_ops":"AND", - "group_type": "sites", - "operation":"AND", - "groups": { - "compute": { - "services":{ - "CREAM-CE":"OR", - "ARC-CE":"OR", - "GRAM5":"OR", - "unicore6.TargetSystemFactory":"OR", - "QCG.Computing":"OR" - }, - "operation":"OR" - }, - "storage": { - "services":{ - "SRM":"OR", - "SRMv2":"OR" - }, - "operation":"OR" - }, - "information": { - "services":{ - "Site-BDII":"OR" - }, - "operation":"OR" - } - - } -} \ No newline at end of file + "id": "297c368a-524f-4144-9eb6-924fae5f08fa", + "name": "ap1", + "namespace": "test", + "endpoint_group": "sites", + "metric_operation": "AND", + "profile_operation": "AND", + "metric_profile": { + "name": "CH.CERN.SAM.ARGO_MON_CRITICAL", + "id": "c81fdb7b-d8f8-4ff9-96c5-6a0c336e2b25" + }, + "groups": [ + { + "name": "compute", + "operation": "OR", + "services": [ + { + "name": "CREAM-CE", + "operation": "OR" + }, + { + "name": "ARC-CE", + "operation": "OR" + }, + { + "name": "GRAM5", + "operation": "OR" + }, + { + "name": "unicore6.TargetSystemFactory", + "operation": "OR" + }, + { + "name": "QCG.Computing", + "operation": "OR" + } + ] + }, + { + "name": "storage", + "operation": "OR", + "services": [ + { + "name": "SRMv2", + "operation": "OR" + }, + { + "name": "SRM", + "operation": "OR" + } + ] + }, + { + "name": "information", + "operation": "OR", + "services": [ + { + "name": "Site-BDII", + "operation": "OR" + } + ] + } + ] + } diff --git a/flink_jobs/stream_status/src/main/resources/ops/ap2.json b/flink_jobs/stream_status/src/main/resources/ops/ap2.json index e7eb2c7c..fda7868f 100644 --- a/flink_jobs/stream_status/src/main/resources/ops/ap2.json +++ b/flink_jobs/stream_status/src/main/resources/ops/ap2.json @@ -1,35 +1,54 @@ { + "id": "337c368a-524f-4144-9eb6-924fae5f08fa", "name": "fedcloud", "namespace": "egi", - "metric_profile": "ch.cern.sam.CLOUD-MON", - "metric_ops":"AND", - "group_type": "sites", - "operation":"AND", - "groups": { - "accounting": { - "services":{ - "eu.egi.cloud.accounting":"OR" - }, - "operation":"OR" + "endpoint_group": "sites", + "metric_operation": "AND", + "profile_operation": "AND", + "metric_profile": { + "name": "ch.cern.sam.CLOUD-MON", + "id": "c88fdb7b-d8f8-4ff9-96c5-6a0c336e2b25" + }, + "groups": [ + { + "name": "accounting", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.accounting", + "operation": "OR" + } + ] }, - "information": { - "services":{ - "eu.egi.cloud.information.bdii":"OR" - }, - "operation":"OR" + { + "name": "information", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.information.bdii", + "operation": "OR" + } + ] }, - "storage-management": { - "services":{ - "eu.egi.cloud.storage-management.cdmi":"OR" - }, - "operation":"OR" + { + "name": "storage-management", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.storage-management.cdmi", + "operation": "OR" + } + ] }, - "vm-management": { - "services":{ - "eu.egi.cloud.vm-management.occi":"OR" - }, - "operation":"OR" + { + "name": "vm-management", + "operation": "OR", + "services": [ + { + "name": "eu.egi.cloud.vm-management.occi", + "operation": "OR" + } + ] } - - } + ] } diff --git a/flink_jobs/stream_status/src/main/resources/ops/config.json b/flink_jobs/stream_status/src/main/resources/ops/config.json index b7a83621..c2c550e5 100644 --- a/flink_jobs/stream_status/src/main/resources/ops/config.json +++ b/flink_jobs/stream_status/src/main/resources/ops/config.json @@ -1,24 +1,83 @@ { - "tenant":"EGI", - "id":"c800846f-8478-4af8-85d1-a3f12fe4c18f", - "job":"Critical", - "egroup":"SITES", - "ggroup":"NGI", - "altg":"ops", - "weight":"hepspec", - "egroup_tags":{ - "scope":"EGI", - "production":"1", - "monitored":"1" - }, - "ggroup_tags":{ - "scope":"EGI", - "infrastructure":"Production", - "certification":"Certified" - }, - "mdata_tags":{ - "vo":"ops", - "vo_fqan":"ops", - "roc":"any" + "id": "c800846f-8478-4af8-85d1-a3f12fe4c18f", + "info": { + "name": "Critical", + "description": "EGI report for Roc critical", + "created": "2015-10-19 10:35:49", + "updated": "2015-10-19 10:35:49" + }, + "tenant": "EGI", + "topology_schema": { + "group": { + "type": "NGI", + "group": { + "type": "SITES" + } + } + }, + "weight": "hepspec", + "profiles": [ + { + "id": "433beb2c-45cc-49d4-a8e0-b132bb30327e", + "name": "ch.cern.sam.ROC_CRITICAL", + "type": "metric" + }, + { + "id": "17d1462f-8f91-4728-a253-1a6e8e2e848d", + "name": "ops1", + "type": "operations" + }, + { + "id": "1ef8c0c9-f9ef-4ca1-9ee7-bb8b36332036", + "name": "critical", + "type": "aggregation" + } + ], + "filter_tags": [ + { + "name": "production", + "value": "1", + "context": "endpoint_groups" + }, + { + "name": "monitored", + "value": "1", + "context": "endpoint_groups" + }, + { + "name": "scope", + "value": "EGI", + "context": "endpoint_groups" + }, + { + "name": "scope", + "value": "EGI", + "context": "group_of_groups" + }, + { + "name": "infrastructure", + "value": "Production", + "context": "group_of_groups" + }, + { + "name": "certification", + "value": "Certified", + "context": "group_of_groups" + }, + { + "name": "vo", + "value": "ops", + "context": "metric_data" + }, + { + "name": "vo_fqan", + "value": "ops", + "context": "metric_data" + }, + { + "name": "roc", + "value": "any", + "context": "metric_data" + } + ] } -} diff --git a/flink_jobs/stream_status/src/test/java/status/StatusManagerTest.java b/flink_jobs/stream_status/src/test/java/status/StatusManagerTest.java index b05cd193..cd10e52b 100644 --- a/flink_jobs/stream_status/src/test/java/status/StatusManagerTest.java +++ b/flink_jobs/stream_status/src/test/java/status/StatusManagerTest.java @@ -41,6 +41,9 @@ public static void setUpBeforeClass() throws Exception { @Test public void test() throws URISyntaxException, IOException, ParseException { + + + // Prepare Resource File URL resAPSJsonFile = StatusManagerTest.class.getResource("/ops/ap1.json"); File jsonAPSFile = new File(resAPSJsonFile.toURI()); @@ -58,7 +61,7 @@ public void test() throws URISyntaxException, IOException, ParseException { File avroDownFile = new File(resDownAvroFile.toURI()); StatusManager sm = new StatusManager(); - sm.report="Critical"; + sm.setReport("Critical"); sm.loadAllFiles("2017-03-03", avroDownFile, avroEGPFile, avroMPSFile, jsonAPSFile, jsonOPSFile); Date ts1 = sm.fromZulu("2017-03-03T00:00:00Z");