Details
| ID | VeloCloudEventCollector |
|---|---|
| Provider | Arista Networks |
| Category | Network Security |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | XSIAM |
README
Collects events from the VeloCloud API.
This integration was integrated and tested with VeloCloud as of 2025-10-06.
Configure VeloCloud Event Collector in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| Enterprise ID | True | |
| API Key | API key for authenticating with Arista VeloCloud | False |
| First fetch timestamp | False | |
| Limit of events per fetch | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
velocloud-get-events
Get events fro`m Arista VeloCloud
Base Command
velocloud-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| start_time | Start time for event query in ISO format or human-readable format (e.g., ‘3 days ago’). Default is 1 day ago. | Optional |
| end_time | End time for event query in ISO format or human-readable format (e.g., ‘now’). Default is now. | Optional |
| limit | Maximum number of events to retrieve. Default is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| VeloCloud.Event.logicalId | String | Event logical ID |
| VeloCloud.Event.category | String | Event category |
| VeloCloud.Event.event | String | Event type |
| VeloCloud.Event.severity | String | Event severity |
| VeloCloud.Event.message | String | Event message |
| VeloCloud.Event.detail | String | Event details, usually in JSON string format |
| VeloCloud.Event.eventTime | Date | Event timestamp |
| VeloCloud.Event.created | Date | Event creation time |
| VeloCloud.Event.edgeName | String | Name of the edge |
| VeloCloud.Event.enterpriseName | String | Name of the enterprise |
| VeloCloud.Event.enterpriseUsername | String | Username associated with the enterprise |
| VeloCloud.Event.segmentName | String | Name of the network segment |
Command Example
!velocloud-get-events limit=1
{
"VeloCloud": {
"Event": {
"category": "EDGE",
"created": "2025-11-03T21:02:36.000Z",
"detail": "{\"last_request_time\":0,\"client_mac\":\"03:11:22:33:44:55\",\"client_ipv4addr\":\"192.168.1.1\",\"hostname\":\"nexusquantum7\",\"os_type\":800,\"os_class\":1,\"os_class_name\":\"OTHER\",\"os_version\":\"\",\"device_type\":\"\",\"os_description\":\"Xerox Printer\",\"dhcp_param_list\":\"6,3,1,15,66,67,13,44,12\",\"segment_id\":0,\"edgeSerialNumber\":\"K7M9N2P5Q8R1\"}",
"edgeName": "radiancecdnc43",
"enterpriseName": "ACME Co.",
"enterpriseUsername": "",
"event": "EDGE_NEW_DEVICE",
"eventTime": "2025-11-03T21:02:16.000Z",
"logicalId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"message": "New or updated client device 03:11:22:33:44:55, ip 192.168.1.1, segId 0, hostname nexusquantum7, os Xerox Printer",
"segmentName": "",
"severity": "NOTICE"
}
}
}
Configuration parameters
url— Server URL (required)enterprise_id— Enterprise ID (required)api_key— (required)first_fetch— First fetch timestamplimit— Limit of events per fetchinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
velocloud-get-eventsGet events fro`m Arista VeloCloud
"""Unit tests for VeloCloudEventCollector integration.""" import json import os from datetime import datetime, timedelta, UTC from unittest.mock import patch import pytest from VeloCloudEventCollector import ( Client, get_events_command, test_module as velocloud_test_module, fetch_events, fetch_events_command, velocloud_parse_date_range, format_events, ) def load_test_data(test_data_filename): """Load test data from a JSON file. Args: test_data_filename: Name of the test data file Returns: Parsed JSON data from the file """ with open(os.path.join("./test_data", test_data_filename)) as f: return json.load(f) @pytest.fixture() def client(requests_mock): """Create a VeloCloud client with mocked requests. Args: requests_mock: requests_mock fixture Returns: Configured VeloCloud Client instance """ base_url = "https://tenant123.velocloud.net" enterprise_id = "12345678" return Client( base_url=base_url, enterprise_id=enterprise_id, verify=False, headers={}, proxy=False, ) class TestGetEventsCommand: """Test get_events_command functionality.""" def test_get_events_command_successful(self, requests_mock, client): """ Given: - VeloCloud client is configured When: - Executing get_events_command Then: - Ensure command returns events with correct structure """ # Load test data events_data = load_test_data("output-get-events.json") # Mock the API response requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": events_data, "metadata": {}}, ) # Prepare command arguments args = { "start_time": "2025-11-02T21:02:42.829772", "end_time": "2025-11-03T21:02:42.831369", "limit": "100", } # Execute command result = get_events_command(client, args) # Assertions assert result.outputs_prefix == "VeloCloud.Event" assert result.outputs_key_field == "logicalId" assert isinstance(result.outputs, list) assert len(result.outputs) == 3 assert result.outputs[0]["event"] == "EDGE_NEW_DEVICE" assert result.outputs[0]["category"] == "EDGE" def test_get_events_command_with_default_values(self, requests_mock, client): """ Given: - VeloCloud client is configured When: - Executing get_events_command without optional parameters Then: - Ensure default values are used """ events_data = load_test_data("output-get-events.json") requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": events_data, "metadata": {}}, ) # Minimal args - let defaults apply args = {} result = get_events_command(client, args) assert result.outputs_prefix == "VeloCloud.Event" assert len(result.outputs) == len(events_data) def test_get_events_command_empty_response(self, requests_mock, client): """ Given: - VeloCloud client is configured When: - API returns no events Then: - Ensure command handles empty response gracefully """ requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": [], "metadata": {}}, ) args = {"limit": "10"} result = get_events_command(client, args) assert isinstance(result.outputs, list) assert len(result.outputs) == 0 class TestTestModule: """Test test_module functionality.""" def test_test_module_success(self, requests_mock, client): """ Given: - VeloCloud client is configured - API is accessible When: - Executing test_module Then: - Ensure 'ok' is returned """ # Load real sample data and use first event sample_events = load_test_data("output-get-events.json") first_event = sample_events[0] requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": [first_event], "metadata": {}}, ) result = velocloud_test_module(client) assert result == "ok" def test_test_module_authentication_error(self, requests_mock, client): """ Given: - VeloCloud client is configured - API authentication fails When: - Executing test_module Then: - Ensure authentication error message is returned """ # Load real sample data for failed auth event failed_auth_event = load_test_data("failed-auth.json") requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", status_code=200, json=failed_auth_event, ) result = velocloud_test_module(client) assert "Invalid API Token" in result class TestVelocloudParseDateRange: """Test velocloud_parse_date_range functionality.""" def test_parse_date_range_with_last_run_time(self): """ Given: - last_run_time is provided When: - Calling velocloud_parse_date_range Then: - Ensure start_time is incremented by 1 second """ first_fetch = "3 days ago" last_run_time = "2025-11-02T10:00:00Z" start_time, end_time = velocloud_parse_date_range(first_fetch, last_run_time) parsed_start = datetime.fromisoformat(start_time) # Should be 1 second after last_run_time expected = datetime(2025, 11, 2, 10, 0, 1, tzinfo=UTC) assert parsed_start == expected def test_parse_date_range_without_last_run_time(self): """ Given: - last_run_time is empty When: - Calling velocloud_parse_date_range Then: - Ensure start_time is based on first_fetch """ first_fetch = "2 days ago" last_run_time = "" start_time, end_time = velocloud_parse_date_range(first_fetch, last_run_time) parsed_start = datetime.fromisoformat(start_time) # Start should be approximately 2 days ago # Using a larger tolerance since dateparser can have variations now = datetime.now(UTC) expected_time = now - timedelta(days=2) delta = abs((parsed_start - expected_time).total_seconds()) assert delta < 86400 # Within 1 day of tolerance def test_parse_date_range_invalid_last_run_time_falls_back_to_first_fetch(self): """ Given: - last_run_time is invalid When: - Calling velocloud_parse_date_range Then: - Ensure start_time falls back to first_fetch """ first_fetch = "1 day ago" last_run_time = "invalid-date-string" start_time, end_time = velocloud_parse_date_range(first_fetch, last_run_time) parsed_start = datetime.fromisoformat(start_time) # Should fall back to first_fetch (1 day ago) now = datetime.now(UTC) expected_time = now - timedelta(days=1) delta = abs((parsed_start - expected_time).total_seconds()) assert delta < 86400 # Within 1 day of tolerance def test_parse_date_range_invalid_both_uses_default(self): """ Given: - Both last_run_time and first_fetch are invalid When: - Calling velocloud_parse_date_range Then: - Ensure start_time defaults to 1 day ago """ first_fetch = "completely-invalid" last_run_time = "" start_time, end_time = velocloud_parse_date_range(first_fetch, last_run_time) parsed_start = datetime.fromisoformat(start_time) # Should default to 1 day ago now = datetime.now(UTC) expected_time = now - timedelta(days=1) delta = abs((parsed_start - expected_time).total_seconds()) assert delta < 60 def test_parse_date_range_end_time_is_now(self): """ Given: - Any valid first_fetch When: - Calling velocloud_parse_date_range Then: - Ensure end_time is approximately now """ first_fetch = "1 day ago" last_run_time = "" start_time, end_time = velocloud_parse_date_range(first_fetch, last_run_time) parsed_end = datetime.fromisoformat(end_time) now = datetime.now(UTC) # End time should be within 5 seconds of now delta = abs((parsed_end - now).total_seconds()) assert delta < 5 class TestFormatEvents: """Test format_events functionality.""" def test_format_events_with_event_time(self): """ Given: - Events with eventTime field When: - Calling format_events Then: - Ensure _time is set to eventTime """ # Load real test data and use first event (has eventTime) events_data = load_test_data("output-get-events.json") events = [events_data[0]] formatted = format_events(events) assert len(formatted) == 1 assert formatted[0]["_time"] == events_data[0]["eventTime"] assert formatted[0]["logicalId"] == events_data[0]["logicalId"] assert formatted[0]["message"] == events_data[0]["message"] def test_format_events_with_created_fallback(self): """ Given: - Events with created field instead of eventTime When: - Calling format_events Then: - Ensure _time is set to created as fallback """ # Create event with only created field (remove eventTime) events_data = load_test_data("output-get-events.json") event = events_data[1].copy() event_created_time = event["created"] del event["eventTime"] events = [event] formatted = format_events(events) assert len(formatted) == 1 assert formatted[0]["_time"] == event_created_time def test_format_events_prioritizes_event_time_over_created(self): """ Given: - Events with both eventTime and created fields When: - Calling format_events Then: - Ensure eventTime is prioritized """ # Load real test data (has both eventTime and created) events_data = load_test_data("output-get-events.json") events = [events_data[0]] formatted = format_events(events) # Should use eventTime, not created assert formatted[0]["_time"] == events_data[0]["eventTime"] assert formatted[0]["_time"] != events_data[0]["created"] def test_format_events_skips_events_without_timestamp(self): """ Given: - Mix of events with and without timestamps When: - Calling format_events Then: - Ensure events without timestamps are skipped """ # Load real test data and create mix with one event missing timestamps events_data = load_test_data("output-get-events.json") event_with_timestamp = events_data[0] event_without_timestamp = events_data[1].copy() del event_without_timestamp["eventTime"] del event_without_timestamp["created"] event_with_created = events_data[2] events = [event_with_timestamp, event_without_timestamp, event_with_created] formatted = format_events(events) assert len(formatted) == 2 logical_ids = [e["logicalId"] for e in formatted] assert event_with_timestamp["logicalId"] in logical_ids assert event_without_timestamp["logicalId"] not in logical_ids assert event_with_created["logicalId"] in logical_ids def test_format_events_empty_list(self): """ Given: - Empty events list When: - Calling format_events Then: - Ensure empty list is returned """ events = [] formatted = format_events(events) assert isinstance(formatted, list) assert len(formatted) == 0 def test_format_events_preserves_all_fields(self): """ Given: - Events with multiple fields When: - Calling format_events Then: - Ensure all original fields are preserved and _time is added """ # Load real test data with all fields events_data = load_test_data("output-get-events.json") events = [events_data[0]] formatted = format_events(events) # Verify _time is added using eventTime assert formatted[0]["_time"] == events_data[0]["eventTime"] # Verify all original fields are preserved assert formatted[0]["logicalId"] == events_data[0]["logicalId"] assert formatted[0]["category"] == events_data[0]["category"] assert formatted[0]["severity"] == events_data[0]["severity"] assert formatted[0]["message"] == events_data[0]["message"] assert formatted[0]["edgeName"] == events_data[0]["edgeName"] assert formatted[0]["event"] == events_data[0]["event"] assert formatted[0]["created"] == events_data[0]["created"] class TestFetchEvents: """Test fetch_events functionality.""" def test_fetch_events_successful(self, requests_mock, client): """ Given: - VeloCloud client is configured - API returns events When: - Calling fetch_events Then: - Ensure events and last_timestamp are returned """ events_data = load_test_data("output-get-events.json") expected_new_timestamp = events_data[0].get("eventTime", "") requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": events_data, "metadata": {}}, ) last_timestamp = "" first_fetch = "1 day ago" fetched_events, new_timestamp = fetch_events(client, last_timestamp, first_fetch, limit=1000) assert isinstance(fetched_events, list) assert len(fetched_events) == len(events_data) assert new_timestamp == expected_new_timestamp def test_fetch_events_with_pagination(self, requests_mock, client): """ Given: - VeloCloud client is configured - API returns events with pagination metadata When: - Calling fetch_events Then: - Ensure pagination is handled correctly """ # Load real pagination test data pagination_data = load_test_data("output-fetch-events-pagination.json") page1 = pagination_data["page1"] page2 = pagination_data["page2"] # First request returns first page with nextPageLink requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", [ {"json": page1}, {"json": page2}, ], ) last_timestamp = "" first_fetch = "1 day ago" fetched_events, new_timestamp = fetch_events(client, last_timestamp, first_fetch, limit=100) # Dynamically extract expected logicalIds from pagination data expected_events = page1["data"] + page2["data"] expected_logicalIds = [event["logicalId"] for event in expected_events] assert len(fetched_events) == len(expected_logicalIds) for i, event in enumerate(fetched_events): assert event["logicalId"] == expected_logicalIds[i] class TestFetchEventsCommand: """Test fetch_events_command functionality.""" @patch("VeloCloudEventCollector.send_events_to_xsiam") @patch("VeloCloudEventCollector.demisto") def test_fetch_events_command_successful(self, mock_demisto, mock_send_events, requests_mock, client): """ Given: - VeloCloud client is configured - demisto functions are mocked When: - Executing fetch_events_command Then: - Ensure events are sent to XSIAM and last run is updated """ events_data = load_test_data("output-get-events.json") requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": events_data, "metadata": {}}, ) # Setup demisto mocks mock_demisto.params.return_value = { "first_fetch": "1 day ago", "max_fetch": 1000, } mock_demisto.getLastRun.return_value = {} fetch_events_command(client) # Verify send_events_to_xsiam was called assert mock_send_events.called # Verify setLastRun was called assert mock_demisto.setLastRun.called @patch("VeloCloudEventCollector.send_events_to_xsiam") @patch("VeloCloudEventCollector.demisto") def test_fetch_events_command_with_last_run(self, mock_demisto, mock_send_events, requests_mock, client): """ Given: - VeloCloud client is configured - Last run timestamp exists When: - Executing fetch_events_command Then: - Ensure only new events are fetched """ events_data = load_test_data("output-get-events.json") requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": events_data, "metadata": {}}, ) last_run_timestamp = "2025-11-02T10:00:00Z" mock_demisto.params.return_value = { "first_fetch": "1 day ago", "max_fetch": 1000, } mock_demisto.getLastRun.return_value = {"last_event_time": last_run_timestamp} fetch_events_command(client) assert mock_send_events.called assert mock_demisto.setLastRun.called @patch("VeloCloudEventCollector.send_events_to_xsiam") @patch("VeloCloudEventCollector.demisto") def test_fetch_events_command_no_events(self, mock_demisto, mock_send_events, requests_mock, client): """ Given: - VeloCloud client is configured - API returns no events When: - Executing fetch_events_command Then: - Ensure command handles empty response gracefully """ requests_mock.post( "https://tenant123.velocloud.net/portal/rest/event/getEnterpriseEvents", json={"data": [], "metadata": {}}, ) mock_demisto.params.return_value = { "first_fetch": "1 day ago", "max_fetch": 1000, } mock_demisto.getLastRun.return_value = {} fetch_events_command(client) # setLastRun should still be called even with no events assert mock_demisto.setLastRun.called