diff --git a/monitoring/README.md b/monitoring/README.md index f4413145b0..228ddaae13 100644 --- a/monitoring/README.md +++ b/monitoring/README.md @@ -58,7 +58,7 @@ authentication: 1. Set `GOOGLE_APPLICATION_CREDENTIALS` environment variable pointing to that file. -## Samples +## Stackdriver Monitoring Samples To run the Stackdriver Monitoring Samples: @@ -80,17 +80,54 @@ To run the Stackdriver Monitoring Samples: Available commands: create-metric Creates a logging metric. + create-uptime-check Creates an uptime check. delete-metric Deletes a logging metric. + delete-uptime-check Deletes an uptime check config. get-descriptor Gets a logging descriptor. help Displays help for a command list Lists commands list-descriptors Lists logging descriptors. + list-uptime-check-ips Lists Uptime Check IPs. + list-uptime-checks Lists Uptime Check Configs. read-timeseries-align Aggregates metrics for each timeseries. read-timeseries-fields Reads Timeseries fields. read-timeseries-reduce Aggregates metrics across multiple timeseries. read-timeseries-simple Reads a timeseries. write-timeseries Writes a timeseries. +## Stackdriver Monitoring Alert Samples + +To run the Stackdriver Monitoring Alert Samples: + + $ php alerts.php + + Stackdriver Monitoring Alerts + + Usage: + command [options] [arguments] + + Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + + Available commands: + backup-policies Back up alert policies. + create-channel Create a notification channel. + create-policy Create an alert policy. + delete-channel Delete a notification channel. + enable-policies Enable or disable alert policies in a project. + help Displays help for a command + list Lists commands + list-channels List alert channels. + list-policies List alert policies. + replace-channels Replace alert channels. + restore-policies Restore alert policies from a backup. + ## The client library This sample uses the [Google Cloud Client Library for PHP][google-cloud-php]. diff --git a/monitoring/alerts.php b/monitoring/alerts.php new file mode 100644 index 0000000000..6a3df76822 --- /dev/null +++ b/monitoring/alerts.php @@ -0,0 +1,129 @@ +add(new Command('backup-policies')) + ->setDefinition($inputDefinition) + ->setDescription('Back up alert policies.') + ->setCode(function ($input, $output) { + alert_backup_policies( + $input->getArgument('project_id') + ); + }); + +$application->add(new Command('create-channel')) + ->setDefinition($inputDefinition) + ->setDescription('Create a notification channel.') + ->setCode(function ($input, $output) { + alert_create_channel( + $input->getArgument('project_id') + ); + }); + +$application->add(new Command('create-policy')) + ->setDefinition($inputDefinition) + ->setDescription('Create an alert policy.') + ->setCode(function ($input, $output) { + alert_create_policy( + $input->getArgument('project_id') + ); + }); + +$application->add(new Command('delete-channel')) + ->setDefinition(clone $inputDefinition) + ->addArgument('channel_id', InputArgument::REQUIRED, 'The notification channel ID to delete') + ->setDescription('Delete a notification channel.') + ->setCode(function ($input, $output) { + alert_delete_channel( + $input->getArgument('project_id'), + $input->getArgument('channel_id') + ); + }); + +$application->add(new Command('enable-policies')) + ->setDefinition(clone $inputDefinition) + ->addArgument('enable', InputArgument::OPTIONAL, 'Enable or disable the polcies', true) + ->addArgument('filter', InputArgument::OPTIONAL, 'Only enable/disable alert policies that match a filter.') + ->setDescription('Enable or disable alert policies in a project.') + ->setCode(function ($input, $output) { + alert_enable_policies( + $input->getArgument('project_id'), + $input->getArgument('enable'), + $input->getArgument('filter') + ); + }); + +$application->add(new Command('restore-policies')) + ->setDefinition($inputDefinition) + ->setDescription('Restore alert policies from a backup.') + ->setCode(function ($input, $output) { + alert_restore_policies( + $input->getArgument('project_id') + ); + }); + +$application->add(new Command('list-policies')) + ->setDefinition($inputDefinition) + ->setDescription('List alert policies.') + ->setCode(function ($input, $output) { + alert_list_policies( + $input->getArgument('project_id') + ); + }); + +$application->add(new Command('list-channels')) + ->setDefinition($inputDefinition) + ->setDescription('List alert channels.') + ->setCode(function ($input, $output) { + alert_list_channels( + $input->getArgument('project_id') + ); + }); +$application->add(new Command('replace-channels')) + ->setDefinition(clone $inputDefinition) + ->addArgument('policy_id', InputArgument::REQUIRED, 'The policy id') + ->addArgument('channel_id', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'list of channel ids') + ->setDescription('Replace alert channels.') + ->setCode(function ($input, $output) { + alert_replace_channels( + $input->getArgument('project_id'), + $input->getArgument('policy_id'), + (array) $input->getArgument('channel_id') + ); + }); + +// for testing +if (getenv('PHPUNIT_TESTS') === '1') { + return $application; +} + +$application->run(); diff --git a/monitoring/composer.json b/monitoring/composer.json index 8602815e2b..8ac88aa1cd 100644 --- a/monitoring/composer.json +++ b/monitoring/composer.json @@ -1,10 +1,19 @@ { "require": { "symfony/console": "^3.3", - "google/cloud-monitoring": "~0.11" + "google/cloud-monitoring": "^0.13.0" }, "autoload": { "files": [ + "src/alert_backup_policies.php", + "src/alert_create_channel.php", + "src/alert_create_policy.php", + "src/alert_delete_channel.php", + "src/alert_enable_policies.php", + "src/alert_list_channels.php", + "src/alert_list_policies.php", + "src/alert_replace_channels.php", + "src/alert_restore_policies.php", "src/create_metric.php", "src/create_uptime_check.php", "src/delete_metric.php", diff --git a/monitoring/phpunit.xml.dist b/monitoring/phpunit.xml.dist index 9d297ccb03..49e0779afd 100644 --- a/monitoring/phpunit.xml.dist +++ b/monitoring/phpunit.xml.dist @@ -22,7 +22,9 @@ + alerts.php quickstart.php + monitoring.php diff --git a/monitoring/src/alert_backup_policies.php b/monitoring/src/alert_backup_policies.php new file mode 100644 index 0000000000..1a8918e280 --- /dev/null +++ b/monitoring/src/alert_backup_policies.php @@ -0,0 +1,61 @@ + $projectId, + ]); + $channelClient = new NotificationChannelServiceClient([ + 'projectId' => $projectId, + ]); + $projectName = $alertClient->projectName($projectId); + + $record = [ + 'project_name' => $projectName, + 'policies' => [], + 'channels' => [], + ]; + $policies = $alertClient->listAlertPolicies($projectName); + foreach ($policies->iterateAllElements() as $policy) { + $record['policies'][] = json_decode($policy->serializeToJsonString()); + } + $channels = $channelClient->listNotificationChannels($projectName); + foreach ($channels->iterateAllElements() as $channel) { + $record['channels'][] = json_decode($channel->serializeToJsonString()); + } + file_put_contents('backup.json', json_encode($record, JSON_PRETTY_PRINT)); + print('Backed up alert policies and notification channels to backup.json.'); +} +// [END monitoring_alert_backup_policies] diff --git a/monitoring/src/alert_create_channel.php b/monitoring/src/alert_create_channel.php new file mode 100644 index 0000000000..ecb0adc44c --- /dev/null +++ b/monitoring/src/alert_create_channel.php @@ -0,0 +1,48 @@ + $projectId, + ]); + $projectName = $channelClient->projectName($projectId); + + $channel = new NotificationChannel(); + $channel->setDisplayName('Test Notification Channel'); + $channel->setType('email'); + $channel->setLabels(['email_address' => 'fake@example.com']); + + $channel = $channelClient->createNotificationChannel($projectName, $channel); + printf('Created notification channel %s' . PHP_EOL, $channel->getName()); +} +# [END monitoring_alert_create_channel] diff --git a/monitoring/src/alert_create_policy.php b/monitoring/src/alert_create_policy.php new file mode 100644 index 0000000000..9f587b4fb7 --- /dev/null +++ b/monitoring/src/alert_create_policy.php @@ -0,0 +1,62 @@ + $projectId, + ]); + $projectName = $alertClient->projectName($projectId); + + $policy = new AlertPolicy(); + $policy->setDisplayName('Test Alert Policy'); + $policy->setCombiner(ConditionCombinerType::PBOR); + /** @see https://cloud.google.com/monitoring/api/resources for a list of resource.type */ + /** @see https://cloud.google.com/monitoring/api/metrics_gcp for a list of metric.type */ + $policy->setConditions([new Condition([ + 'display_name' => 'condition-1', + 'condition_threshold' => new MetricThreshold([ + 'filter' => 'resource.type = "gce_instance" AND metric.type = "compute.googleapis.com/instance/cpu/utilization"', + 'duration' => new Duration(['seconds' => '60']), + 'comparison' => ComparisonType::COMPARISON_LT, + ]) + ])]); + + $policy = $alertClient->createAlertPolicy($projectName, $policy); + printf('Created alert policy %s' . PHP_EOL, $policy->getName()); +} +# [END monitoring_alert_create_policy] diff --git a/monitoring/src/alert_delete_channel.php b/monitoring/src/alert_delete_channel.php new file mode 100644 index 0000000000..8f5c76fc89 --- /dev/null +++ b/monitoring/src/alert_delete_channel.php @@ -0,0 +1,42 @@ + $projectId, + ]); + $channelName = $channelClient->notificationChannelName($projectId, $channelId); + + $channelClient->deleteNotificationChannel($channelName); + printf('Deleted notification channel %s' . PHP_EOL, $channelName); +} +# [END monitoring_alert_delete_channel] diff --git a/monitoring/src/alert_enable_policies.php b/monitoring/src/alert_enable_policies.php new file mode 100644 index 0000000000..e3fc61a59d --- /dev/null +++ b/monitoring/src/alert_enable_policies.php @@ -0,0 +1,69 @@ + $projectId, + ]); + $projectName = $alertClient->projectName($projectId); + + $policies = $alertClient->listAlertPolicies($projectName, [ + 'filter' => $filter + ]); + foreach ($policies->iterateAllElements() as $policy) { + $isEnabled = $policy->getEnabled()->getValue(); + if ($enable == $isEnabled) { + printf('Policy %s is already %s' . PHP_EOL, + $policy->getName(), + $isEnabled ? 'enabled' : 'disabled' + ); + } else { + $policy->getEnabled()->setValue((bool) $enable); + $mask = new FieldMask(); + $mask->setPaths(['enabled']); + $alertClient->updateAlertPolicy($policy, [ + 'updateMask' => $mask + ]); + printf('%s %s' . PHP_EOL, + $enable ? 'Enabled' : 'Disabled', + $policy->getName() + ); + } + } +} +// [END monitoring_alert_enable_policies] diff --git a/monitoring/src/alert_list_channels.php b/monitoring/src/alert_list_channels.php new file mode 100644 index 0000000000..3f49451fe9 --- /dev/null +++ b/monitoring/src/alert_list_channels.php @@ -0,0 +1,45 @@ + $projectId, + ]); + + $channels = $channelClient->listNotificationChannels( + $channelClient->projectName($projectId) + ); + foreach ($channels->iterateAllElements() as $channel) { + printf('Name: %s (%s)' . PHP_EOL, $channel->getDisplayName(), $channel->getName()); + } +} +// [END monitoring_alert_list_channels] diff --git a/monitoring/src/alert_list_policies.php b/monitoring/src/alert_list_policies.php new file mode 100644 index 0000000000..a7e997de24 --- /dev/null +++ b/monitoring/src/alert_list_policies.php @@ -0,0 +1,51 @@ + $projectId, + ]); + + $policies = $alertClient->listAlertPolicies( + $alertClient->projectName($projectId) + ); + foreach ($policies->iterateAllElements() as $policy) { + printf('Name: %s (%s)' . PHP_EOL, $policy->getDisplayName(), $policy->getName()); + } +} +// [END monitoring_alert_list_policies] diff --git a/monitoring/src/alert_replace_channels.php b/monitoring/src/alert_replace_channels.php new file mode 100644 index 0000000000..76cbe4ee46 --- /dev/null +++ b/monitoring/src/alert_replace_channels.php @@ -0,0 +1,61 @@ + $projectId, + ]); + + $channelClient = new NotificationChannelServiceClient([ + 'projectId' => $projectId, + ]); + $policy = new AlertPolicy(); + $policy->setName($alertClient->alertPolicyName($projectId, $alertPolicyId)); + + $newChannels = []; + foreach ($channelIds as $channelId) { + $newChannels[] = $channelClient->notificationChannelName($projectId, $channelId); + } + $policy->setNotificationChannels($newChannels); + $mask = new FieldMask(); + $mask->setPaths(['notification_channels']); + $updatedPolicy = $alertClient->updateAlertPolicy($policy, [ + 'updateMask' => $mask, + ]); + printf('Updated %s' . PHP_EOL, $updatedPolicy->getName()); +} +// [END monitoring_alert_replace_channels] diff --git a/monitoring/src/alert_restore_policies.php b/monitoring/src/alert_restore_policies.php new file mode 100644 index 0000000000..2722a6a791 --- /dev/null +++ b/monitoring/src/alert_restore_policies.php @@ -0,0 +1,151 @@ + $projectId, + ]); + + $channelClient = new NotificationChannelServiceClient([ + 'projectId' => $projectId, + ]); + + print('Loading alert policies and notification channels from backup.json.' . PHP_EOL); + $projectName = $alertClient->projectName($projectId); + $record = json_decode(file_get_contents('backup.json'), true); + $isSameProject = $projectName == $record['project_name']; + + # Convert dicts to AlertPolicies. + $policies = []; + foreach ($record['policies'] as $policyArray) { + $policy = new AlertPolicy(); + $policy->mergeFromJsonString(json_encode($policyArray)); + $policies[] = $policy; + } + + # Convert dicts to NotificationChannels + $channels = []; + foreach (array_filter($record['channels']) as $channelArray) { + $channel = new NotificationChannel(); + $channel->mergeFromJsonString(json_encode($channelArray)); + $channels[] = $channel; + } + + # Restore the channels. + $channelNameMap = []; + foreach ($channels as $channel) { + $updated = false; + printf('Updating channel %s' . PHP_EOL, $channel->getDisplayName()); + + # This field is immutable and it is illegal to specify a + # non-default value (UNVERIFIED or VERIFIED) in the + # Create() or Update() operations. + $channel->setVerificationStatus( + VerificationStatus::VERIFICATION_STATUS_UNSPECIFIED + ); + + if ($isSameProject) { + try { + $channelClient->updateNotificationChannel($channel); + $updated = true; + } catch (ApiException $e) { + # The channel was deleted. Create it below. + if ($e->getStatus() !== 'NOT_FOUND') { + throw $e; + } + } + } + + if (!$updated) { + # The channel no longer exists. Recreate it. + $oldName = $channel->getName(); + $channel->setName(''); + $newChannel = $channelClient->createNotificationChannel( + $projectName, + $channel + ); + $channelNameMap[$oldName] = $newChannel->getName(); + } + } + + # Restore the alerts + foreach ($policies as $policy) { + printf('Updating policy %s' . PHP_EOL, $policy->getDisplayName()); + # These two fields cannot be set directly, so clear them. + $policy->setCreationRecord(null); + $policy->setMutationRecord(null); + + $notificationChannels = $policy->getNotificationChannels(); + + # Update old channel names with new channel names. + foreach ($notificationChannels as $i => $channel) { + if (isset($channelNameMap[$channel])) { + $notificationChannels[$i] = $channelNameMap[$channel]; + } + } + + $updated = false; + if ($isSameProject) { + try { + $alertClient->updateAlertPolicy($policy); + $updated = true; + } catch (ApiException $e) { + # The policy was deleted. Create it below. + if ($e->getStatus() !== 'NOT_FOUND') { + throw $e; + } + } + } + + if (!$updated) { + # The policy no longer exists. Recreate it. + $oldName = $policy->getName(); + $policy->setName(''); + foreach ($policy->getConditions() as $condition) { + $condition->setName(''); + } + $policy = $alertClient->createAlertPolicy($projectName, $policy); + } + printf('Updated %s' . PHP_EOL, $policy->getName()); + } + print('Restored alert policies and notification channels from backup.json.'); +} +# [END monitoring_alert_enable_channel] +# [END monitoring_alert_restore_policies] +# [END monitoring_alert_update_channel] diff --git a/monitoring/test/alertsTest.php b/monitoring/test/alertsTest.php new file mode 100644 index 0000000000..0456f722be --- /dev/null +++ b/monitoring/test/alertsTest.php @@ -0,0 +1,194 @@ +runAlertCommand('create-policy'); + $this->assertRegexp($regexp, $output); + + // Save the policy ID for later + preg_match($regexp, $output, $matches); + self::$policyId = $matches[1]; + } + + public function testEnablePolicies() + { + $policyName = AlertPolicyServiceClient::alertPolicyName(self::$projectId, self::$policyId); + $output = $this->runAlertCommand('enable-policies', [ + 'filter' => sprintf('name = "%s"', $policyName), + 'enable' => true, + ]); + $this->assertContains( + sprintf('Policy %s is already enabled', $policyName), + $output + ); + + $output = $this->runAlertCommand('enable-policies', [ + 'filter' => sprintf('name = "%s"', $policyName), + 'enable' => false, + ]); + + $this->assertContains(sprintf('Disabled %s', $policyName), $output); + } + + /** @depends testCreatePolicy */ + public function testCreateChannel() + { + $regexp = '/^Created notification channel projects\/[\w-]+\/notificationChannels\/(\d+)$/'; + $output = $this->runAlertCommand('create-channel'); + $this->assertRegexp($regexp, $output); + + // Save the channel ID for later + preg_match($regexp, $output, $matches); + self::$channelId = $matches[1]; + } + + /** @depends testCreateChannel */ + public function testReplaceChannel() + { + $alertClient = new AlertPolicyServiceClient(); + $channelClient = new NotificationChannelServiceClient(); + $policyName = $alertClient->alertPolicyName(self::$projectId, self::$policyId); + + $regexp = '/^Created notification channel projects\/[\w-]+\/notificationChannels\/(\d+)$/'; + $output = $this->runAlertCommand('create-channel'); + $this->assertRegexp($regexp, $output); + preg_match($regexp, $output, $matches); + $channelId1 = $matches[1]; + + $output = $this->runAlertCommand('create-channel'); + $this->assertRegexp($regexp, $output); + preg_match($regexp, $output, $matches); + $channelId2 = $matches[1]; + + $output = $this->runAlertCommand('replace-channels', [ + 'policy_id' => self::$policyId, + 'channel_id' => [$channelId1, $channelId2] + ]); + $this->assertContains(sprintf('Updated %s', $policyName), $output); + + // verify the new channels have been added to the policy + $policy = $alertClient->getAlertPolicy($policyName); + $channels = $policy->getNotificationChannels(); + $this->assertEquals(2, count($channels)); + $this->assertEquals( + $newChannelName1 = $channelClient->notificationChannelName(self::$projectId, $channelId1), + $channels[0] + ); + $this->assertEquals( + $newChannelName2 = $channelClient->notificationChannelName(self::$projectId, $channelId2), + $channels[1] + ); + + $output = $this->runAlertCommand('replace-channels', [ + 'policy_id' => self::$policyId, + 'channel_id' => self::$channelId, + ]); + $this->assertContains(sprintf('Updated %s', $policyName), $output); + + // verify the new channel replaces the previous channels added to the policy + $policy = $alertClient->getAlertPolicy($policyName); + $channels = $policy->getNotificationChannels(); + $this->assertEquals(1, count($channels)); + $this->assertEquals( + $channelClient->notificationChannelName(self::$projectId, self::$channelId), + $channels[0] + ); + + // remove the old chnnels + $channelClient->deleteNotificationChannel($newChannelName1); + $channelClient->deleteNotificationChannel($newChannelName2); + } + + /** @depends testCreatePolicy */ + public function testListPolciies() + { + // backup + $output = $this->runAlertCommand('list-policies'); + $this->assertContains(self::$policyId, $output); + } + + /** @depends testCreateChannel */ + public function testListChannels() + { + // backup + $output = $this->runAlertCommand('list-channels'); + $this->assertContains(self::$channelId, $output); + } + + /** @depends testCreateChannel */ + public function testBackupAndRestore() + { + // backup + $output = $this->runAlertCommand('backup-policies'); + $this->assertContains('Backed up alert policies', $output); + + $backupJson = file_get_contents(__DIR__ . '/../backup.json'); + $backup = json_decode($backupJson, true); + $this->assertArrayHasKey('policies', $backup); + $this->assertArrayHasKey('channels', $backup); + $this->assertGreaterThan(0, count($backup['policies'])); + $this->assertGreaterThan(0, count($backup['channels'])); + $this->assertContains(self::$policyId, $backupJson); + $this->assertContains(self::$channelId, $backupJson); + + // restore + $output = $this->runAlertCommand('restore-policies'); + $this->assertContains('Restored alert policies', $output); + } + + /** @depends testCreatePolicy */ + public function testDeleteChannel() + { + // delete the policy first (required in order to delete the channel) + $alertClient = new AlertPolicyServiceClient(); + $alertClient->deleteAlertPolicy( + $alertClient->alertPolicyName(self::$projectId, self::$policyId) + ); + + $output = $this->runAlertCommand('delete-channel', [ + 'channel_id' => self::$channelId, + ]); + $this->assertContains('Deleted notification channel', $output); + $this->assertContains(self::$channelId, $output); + } + + public function runAlertCommand($command, $args = []) + { + return $this->runCommand($command, $args + [ + 'project_id' => self::$projectId + ]); + } +}