ruby_aem_aws_odysseas 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/conf/gem.yaml +1 -0
- data/lib/ruby_aem_aws/abstract/cloudwatch.rb +83 -0
- data/lib/ruby_aem_aws/abstract/component.rb +54 -0
- data/lib/ruby_aem_aws/abstract/grouped_component.rb +39 -0
- data/lib/ruby_aem_aws/abstract/single_component.rb +36 -0
- data/lib/ruby_aem_aws/abstract/snapshot.rb +35 -0
- data/lib/ruby_aem_aws/abstract/stackmanager.rb +68 -0
- data/lib/ruby_aem_aws/architecture/consolidated_stack.rb +49 -0
- data/lib/ruby_aem_aws/architecture/full_set_stack.rb +141 -0
- data/lib/ruby_aem_aws/architecture/stack_manager.rb +44 -0
- data/lib/ruby_aem_aws/client/cloudwatch.rb +87 -0
- data/lib/ruby_aem_aws/client/dynamo_db.rb +36 -0
- data/lib/ruby_aem_aws/client/s3.rb +42 -0
- data/lib/ruby_aem_aws/client/sns_topic.rb +30 -0
- data/lib/ruby_aem_aws/component/author.rb +71 -0
- data/lib/ruby_aem_aws/component/author_dispatcher.rb +84 -0
- data/lib/ruby_aem_aws/component/author_primary.rb +62 -0
- data/lib/ruby_aem_aws/component/author_publish_dispatcher.rb +56 -0
- data/lib/ruby_aem_aws/component/author_standby.rb +62 -0
- data/lib/ruby_aem_aws/component/chaos_monkey.rb +78 -0
- data/lib/ruby_aem_aws/component/component_descriptor.rb +28 -0
- data/lib/ruby_aem_aws/component/orchestrator.rb +78 -0
- data/lib/ruby_aem_aws/component/publish.rb +78 -0
- data/lib/ruby_aem_aws/component/publish_dispatcher.rb +83 -0
- data/lib/ruby_aem_aws/component/stack_manager_resources.rb +90 -0
- data/lib/ruby_aem_aws/constants.rb +53 -0
- data/lib/ruby_aem_aws/error.rb +68 -0
- data/lib/ruby_aem_aws/mixins/healthy_count_verifier.rb +154 -0
- data/lib/ruby_aem_aws/mixins/healthy_resource_verifier.rb +188 -0
- data/lib/ruby_aem_aws/mixins/healthy_state_verifier.rb +45 -0
- data/lib/ruby_aem_aws/mixins/instance_describer.rb +34 -0
- data/lib/ruby_aem_aws/mixins/metric_verifier.rb +189 -0
- data/lib/ruby_aem_aws/mixins/snapshot_verifier.rb +37 -0
- data/lib/ruby_aem_aws_odysseas.rb +152 -0
- metadata +148 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2018 Shine Solutions
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative '../abstract/stackmanager'
|
16
|
+
require_relative '../client/sns_topic'
|
17
|
+
require_relative '../client/dynamo_db'
|
18
|
+
require_relative '../client/s3'
|
19
|
+
require_relative '../mixins/metric_verifier'
|
20
|
+
|
21
|
+
module RubyAemAws
|
22
|
+
module Component
|
23
|
+
# Interface to the AWS StackManager to send out commands
|
24
|
+
class StackManagerResources
|
25
|
+
attr_reader :dynamodb_client, :s3_client, :s3_resource, :cloud_watch_client, :cloud_watch_log_client
|
26
|
+
include AbstractStackManager
|
27
|
+
include DynamoDB
|
28
|
+
include MetricVerifier
|
29
|
+
include SNSTopic
|
30
|
+
include S3Access
|
31
|
+
|
32
|
+
# @param dynamodb_client AWS DynamoDB client connection
|
33
|
+
# @param params Array of AWS Clients and Resource connections:
|
34
|
+
# - CloudWatchClient: AWS Cloudwatch Client.
|
35
|
+
# - CloudWatchLogsClient: AWS Cloudwatch Logs Client.
|
36
|
+
# - DynamoDBClient: AWS DynamoDB Client.
|
37
|
+
# - S3Client: AWS S3 Client.
|
38
|
+
# - S3Resource: AWS S3 Resource connection.
|
39
|
+
# @return new RubyAemAws::StackManager::StackManagerResources
|
40
|
+
def initialize(params)
|
41
|
+
@dynamodb_client = params[:DynamoDBClient]
|
42
|
+
@s3_client = params[:S3Client]
|
43
|
+
@s3_resource = params[:S3Resource]
|
44
|
+
@cloud_watch_client = params[:CloudWatchLogsClient]
|
45
|
+
@cloud_watch_log_client = params[:CloudWatchLogsClient]
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param topicarn AWS SNS-Topic ARN
|
49
|
+
# @param task AEM StackManager task
|
50
|
+
# @param stack_prefix AEM Stack-Prefix
|
51
|
+
# @param details AEM StackManager task detail message
|
52
|
+
# @return AWS SNS publish message id
|
53
|
+
def sns_publish(topicarn, task, stack_prefix, details)
|
54
|
+
details = JSON.generate(details).tr('\"', '\'')
|
55
|
+
publish(topicarn, message_for_sns_publish(task, stack_prefix, details))
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param dynamodb_tablename AWS DynamoDB table name
|
59
|
+
# @param attribute_value Attribute value to scan for
|
60
|
+
# @return Scan result
|
61
|
+
def dyn_db_msg_scan(dynamodb_tablename, attribute_value)
|
62
|
+
scan(filter_for_db_scan(dynamodb_tablename, attribute_value))
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param dynamodb_tablename AWS DynamoDB table name
|
66
|
+
# @param attribute_value Attribute value to query for
|
67
|
+
# @return Command state
|
68
|
+
def dyn_db_cmd_query(dynamodb_tablename, attribute_value)
|
69
|
+
key_attribute_value = attribute_value.items[0]['command_id']
|
70
|
+
state = query(filter_for_db_query(dynamodb_tablename, key_attribute_value))
|
71
|
+
state.items[0]['state']
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param s3_bucket_name S3 bucketname
|
75
|
+
# @param s3_object_name S3 Object name
|
76
|
+
# @return AWS S3 resource object
|
77
|
+
def s3_resource_object(s3_bucket_name, s3_object_name)
|
78
|
+
get_s3_bucket_object(s3_bucket_name, s3_object_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param s3_bucket_name S3 bucketname
|
82
|
+
# @param s3_object_name S3 Object name
|
83
|
+
# @param dest_path local download path, default: nil
|
84
|
+
# @return AWS S3 client object
|
85
|
+
def s3_download_object(s3_bucket_name, s3_object_name, dest_path = nil)
|
86
|
+
get_s3_object(s3_bucket_name, s3_object_name, dest_path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright 2018 Shine Solutions
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module RubyAemAws
|
16
|
+
class InstanceState
|
17
|
+
PENDING = 'pending'.freeze
|
18
|
+
RUNNING = 'running'.freeze
|
19
|
+
SHUTTING_DOWN = 'shutting_down'.freeze
|
20
|
+
TERMINATED = 'terminated'.freeze
|
21
|
+
STOPPING = 'stopping'.freeze
|
22
|
+
STOPPED = 'stopped'.freeze
|
23
|
+
|
24
|
+
ALL_ACTIVE = [PENDING, RUNNING, SHUTTING_DOWN, STOPPING, STOPPED].freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
# API InstanceState codes https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceState.html
|
28
|
+
class InstanceStateCode
|
29
|
+
PENDING = 0
|
30
|
+
RUNNING = 16
|
31
|
+
SHUTTING_DOWN = 32
|
32
|
+
TERMINATED = 48
|
33
|
+
STOPPING = 64
|
34
|
+
STOPPED = 80
|
35
|
+
end
|
36
|
+
|
37
|
+
class ELBInstanceState
|
38
|
+
INSERVICE = 'InService'.freeze
|
39
|
+
OUTOFSERVICE = 'OutOfService'.freeze
|
40
|
+
|
41
|
+
ALL_ACTIVE = [INSERVICE, OUTOFSERVICE].freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
class Constants
|
45
|
+
REGION_DEFAULT = ENV['AWS_DEFAULT_REGION'] || ENV['aws_default_region'] || 'ap-southeast-2'.freeze
|
46
|
+
ACCESS_KEY_ID = ENV['AWS_ACCESS_KEY_ID'] || ENV['aws_access_key_id']
|
47
|
+
SECRET_ACCESS_KEY = ENV['AWS_SECRET_ACCESS_KEY'] || ENV['aws_scret_access_key']
|
48
|
+
PROFILE = ENV['AWS_PROFILE']
|
49
|
+
INSTANCE_STATE_HEALTHY = RubyAemAws::InstanceState::RUNNING.freeze
|
50
|
+
INSTANCE_STATE_CODE_RUNNING = RubyAemAws::InstanceStateCode::RUNNING
|
51
|
+
ELB_INSTANCE_INSERVICE = RubyAemAws::ELBInstanceState::INSERVICE.freeze
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Copyright 2018 Shine Solutions
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module RubyAemAws
|
16
|
+
# Raise this when a method is not yet implemented.
|
17
|
+
class NotYetImplementedError < StandardError
|
18
|
+
def initialize(msg = 'Not yet implemented')
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raise this when ELB is misconfigured.
|
24
|
+
class ELBMisconfiguration < StandardError
|
25
|
+
def initialize(msg = 'ELB misconfigured')
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raise this when ASG is misconfigured.
|
31
|
+
class ASGMisconfiguration < StandardError
|
32
|
+
def initialize(msg = 'ASG misconfigured')
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raise this when a unknown Response received.
|
38
|
+
class UnknownResponse < StandardError
|
39
|
+
def initialize(msg = 'Unknown response code')
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Raise this when a component unexpectedly has more than one instance.
|
45
|
+
class ExpectedSingleInstanceError < StandardError
|
46
|
+
# def initialize(count, descriptor)
|
47
|
+
# message << 'Expected exactly one instance'
|
48
|
+
# message << " but got #{count}" unless count.nil?
|
49
|
+
# message << "for (#{descriptor.stack_prefix}, #{descriptor.ec2.component}, #{descriptor.ec2.name})" unless descriptor.nil?
|
50
|
+
# super(message)
|
51
|
+
# end
|
52
|
+
|
53
|
+
def initialize(msg = 'Expected exactly one instance')
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Raise this when credentials can't be found
|
59
|
+
class ArgumentError < StandardError
|
60
|
+
def initialize(msg = "No credentials found!
|
61
|
+
Set environment variable AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY or AWS_PROFILE.
|
62
|
+
Alternative use following syntax:
|
63
|
+
RubyAemAws::AemAws.new(aws_access_key_id, aws_scret_access_key) or
|
64
|
+
RubyAemAws::AemAws.new(credentials_profile_name)")
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# Copyright 2018 Shine Solutions
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative '../constants'
|
16
|
+
|
17
|
+
module RubyAemAws
|
18
|
+
# Mixin for checking health of a component via ELB 'healthy' count vs ASG desired_capacity.
|
19
|
+
# Add this to a component to make it capable of determining its own health.
|
20
|
+
module HealthyCountVerifier
|
21
|
+
# Aggregate health_states considered healthy.
|
22
|
+
# @return health_state is ready or scaling.
|
23
|
+
def healthy?
|
24
|
+
%i[ready scaling].include? health_state
|
25
|
+
end
|
26
|
+
|
27
|
+
# Provides detail of the state of the instances comprising the component.
|
28
|
+
# @return one of:
|
29
|
+
# - no_asg: AutoScalingGroup could not be located (by StackPrefix and Component tags).
|
30
|
+
# - no_elb: ElasticLoadBalancer could not be located (by StackPrefix and aws:cloudformation:logical-id tags).
|
31
|
+
# - misconfigured: AutoScalingGroup.desired_capacity is less than 1.
|
32
|
+
# - recovering: ELB running instance count is less than AutoScalingGroup.desired_capacity.
|
33
|
+
# - scaling: ELB running instance count is more than AutoScalingGroup.desired_capacity.
|
34
|
+
# - ready: ELB running instance count is equal to AutoScalingGroup.desired_capacity.
|
35
|
+
def health_state
|
36
|
+
asg = find_auto_scaling_group(asg_client)
|
37
|
+
return :no_asg if asg.nil?
|
38
|
+
|
39
|
+
# Debug:
|
40
|
+
# unless asg.nil?
|
41
|
+
# puts("ASG: #{asg} #{asg.auto_scaling_group_name} (#{asg.desired_capacity})")
|
42
|
+
# asg.instances.each do |i|
|
43
|
+
# puts(" Instance #{i.instance_id}: #{i.health_status}")
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
elb = find_elb(elb_client)
|
48
|
+
return :no_elb if elb.nil?
|
49
|
+
|
50
|
+
elb_running_instances = 0
|
51
|
+
get_instances_state_from_elb(elb).each do |i|
|
52
|
+
elb_running_instances += 1 if i[:state] == RubyAemAws::Constants::INSTANCE_STATE_HEALTHY
|
53
|
+
end
|
54
|
+
|
55
|
+
desired_capacity = asg.desired_capacity
|
56
|
+
|
57
|
+
return :misconfigured if desired_capacity < 1
|
58
|
+
return :recovering if elb_running_instances < desired_capacity
|
59
|
+
return :scaling if elb_running_instances > desired_capacity
|
60
|
+
|
61
|
+
:ready
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return true, if all EC2 instances within the ELB are running
|
65
|
+
def wait_until_healthy
|
66
|
+
raise ELBMisconfiguration if health_state.eql?(:misconfigured)
|
67
|
+
|
68
|
+
sleep 60 while health_state.eql?(:recovering) || health_state.eql?(:scaling)
|
69
|
+
return true if health_state.eql?(:ready)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# @return AutoScalingGroup by StackPrefix and Component tags.
|
75
|
+
def find_auto_scaling_group(asg_client)
|
76
|
+
autoscaling_groups = asg_client.describe_auto_scaling_groups(max_records: 50)
|
77
|
+
find_auto_scaling_group = find_auto_scaling_group_name(autoscaling_groups)
|
78
|
+
|
79
|
+
return find_auto_scaling_group unless find_auto_scaling_group.nil?
|
80
|
+
|
81
|
+
until autoscaling_groups.next_token.nil?
|
82
|
+
autoscaling_groups = asg_client.describe_auto_scaling_groups(max_records: 50, next_token: autoscaling_groups.next_token)
|
83
|
+
find_auto_scaling_group = find_auto_scaling_group_name(autoscaling_groups)
|
84
|
+
return find_auto_scaling_group unless find_auto_scaling_group.nil?
|
85
|
+
end
|
86
|
+
return nil if find_auto_scaling_group.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_auto_scaling_group_name(autoscaling_groups)
|
90
|
+
autoscaling_groups.auto_scaling_groups.each do |autoscaling_group|
|
91
|
+
asg_matches_stack_prefix = false
|
92
|
+
asg_matches_component = false
|
93
|
+
tags = autoscaling_group.tags
|
94
|
+
tags.each do |tag|
|
95
|
+
if tag.key == 'StackPrefix' && tag.value == descriptor.stack_prefix
|
96
|
+
asg_matches_stack_prefix = true
|
97
|
+
break if asg_matches_component
|
98
|
+
|
99
|
+
next
|
100
|
+
end
|
101
|
+
if tag.key == 'Component' && tag.value == descriptor.ec2.component
|
102
|
+
asg_matches_component = true
|
103
|
+
break if asg_matches_stack_prefix
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return autoscaling_group if asg_matches_stack_prefix && asg_matches_component
|
107
|
+
end
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return ElasticLoadBalancer by StackPrefix and logical-id tags.
|
112
|
+
def find_elb(elb_client)
|
113
|
+
elbs = elb_client.describe_load_balancers.load_balancer_descriptions
|
114
|
+
elbs.each do |elb|
|
115
|
+
elb_matches_stack_prefix = false
|
116
|
+
elb_matches_logical_id = false
|
117
|
+
tag_descriptions = elb_client.describe_tags(load_balancer_names: [elb.load_balancer_name]).tag_descriptions
|
118
|
+
next if tag_descriptions.empty?
|
119
|
+
|
120
|
+
tags = tag_descriptions[0].tags
|
121
|
+
tags.each do |tag|
|
122
|
+
if tag.key == 'StackPrefix' && tag.value == descriptor.stack_prefix
|
123
|
+
elb_matches_stack_prefix = true
|
124
|
+
break if elb_matches_logical_id
|
125
|
+
|
126
|
+
next
|
127
|
+
end
|
128
|
+
if tag.key == 'aws:cloudformation:logical-id' && tag.value == descriptor.elb.id
|
129
|
+
elb_matches_logical_id = true
|
130
|
+
break if elb_matches_stack_prefix
|
131
|
+
end
|
132
|
+
end
|
133
|
+
return elb if elb_matches_stack_prefix && elb_matches_logical_id
|
134
|
+
end
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_instances_state_from_elb(elb)
|
139
|
+
stack_prefix_instances = []
|
140
|
+
elb.instances.each do |i|
|
141
|
+
instance = get_instance_by_id(i.instance_id)
|
142
|
+
next if instance.nil?
|
143
|
+
|
144
|
+
instance.tags.each do |tag|
|
145
|
+
next if tag.key != 'StackPrefix'
|
146
|
+
break if tag.value != descriptor.stack_prefix
|
147
|
+
|
148
|
+
stack_prefix_instances.push(id: i.instance_id, state: instance.state.name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
stack_prefix_instances
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# Copyright 2018 Shine Solutions
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative '../constants'
|
16
|
+
|
17
|
+
module RubyAemAws
|
18
|
+
# Mixin for checking health of a component via ELB 'healthy' count vs ASG desired_capacity.
|
19
|
+
# Add this to a component to make it capable of determining its own health.
|
20
|
+
module HealthyResourceVerifier
|
21
|
+
# Aggregate health_states considered healthy.
|
22
|
+
# @return health_state_elb is ready or scaling.
|
23
|
+
def healthy_elb?
|
24
|
+
%i[ready scaling].include? health_state_elb
|
25
|
+
end
|
26
|
+
|
27
|
+
# Aggregate health_states considered healthy.
|
28
|
+
# @return health_state_asg is ready or scaling.
|
29
|
+
def healthy_asg?
|
30
|
+
%i[ready scaling].include? health_state_asg
|
31
|
+
end
|
32
|
+
|
33
|
+
# Provides detail of the state of the instances comprising the component.
|
34
|
+
# @return one of:
|
35
|
+
# - no_asg: AutoScalingGroup could not be located (by StackPrefix and Component tags).
|
36
|
+
# - no_elb: ElasticLoadBalancer could not be located (by StackPrefix and aws:cloudformation:logical-id tags).
|
37
|
+
# - misconfigured: AutoScalingGroup.desired_capacity is less than 1.
|
38
|
+
# - recovering: ELB running instance count is less than AutoScalingGroup.desired_capacity.
|
39
|
+
# - scaling: ELB running instance count is more than AutoScalingGroup.desired_capacity.
|
40
|
+
# - ready: ELB running instance count is equal to AutoScalingGroup.desired_capacity.
|
41
|
+
def health_state_elb
|
42
|
+
asg = find_auto_scaling_group(asg_client)
|
43
|
+
return :no_asg if asg.nil?
|
44
|
+
|
45
|
+
# Debug:
|
46
|
+
# unless asg.nil?
|
47
|
+
# puts("ASG: #{asg} #{asg.auto_scaling_group_name} (#{asg.desired_capacity})")
|
48
|
+
# asg.instances.each do |i|
|
49
|
+
# puts(" Instance #{i.instance_id}: #{i.health_status}")
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
elb = find_elb(elb_client)
|
54
|
+
return :no_elb if elb.nil?
|
55
|
+
|
56
|
+
elb_instance_state = elb_client.describe_instance_health(load_balancer_name: elb.load_balancer_name)
|
57
|
+
|
58
|
+
elb_running_instances = 0
|
59
|
+
elb_instance_state.instance_states.each do |i|
|
60
|
+
elb_running_instances += 1 if i.state == RubyAemAws::Constants::ELB_INSTANCE_INSERVICE
|
61
|
+
end
|
62
|
+
|
63
|
+
desired_capacity = asg.desired_capacity
|
64
|
+
|
65
|
+
return :misconfigured if desired_capacity < 1
|
66
|
+
return :recovering if elb_running_instances < desired_capacity
|
67
|
+
return :scaling if elb_running_instances > desired_capacity
|
68
|
+
|
69
|
+
:ready
|
70
|
+
end
|
71
|
+
|
72
|
+
# Provides detail of the state of the instances comprising the component.
|
73
|
+
# @return one of:
|
74
|
+
# - no_asg: AutoScalingGroup could not be located (by StackPrefix and Component tags).
|
75
|
+
# - misconfigured: AutoScalingGroup.desired_capacity is less than 1.
|
76
|
+
# - recovering: ELB running instance count is less than AutoScalingGroup.desired_capacity.
|
77
|
+
# - scaling: ELB running instance count is more than AutoScalingGroup.desired_capacity.
|
78
|
+
# - ready: ELB running instance count is equal to AutoScalingGroup.desired_capacity.
|
79
|
+
def health_state_asg
|
80
|
+
asg = find_auto_scaling_group(asg_client)
|
81
|
+
return :no_asg if asg.nil?
|
82
|
+
|
83
|
+
# Debug:
|
84
|
+
# unless asg.nil?
|
85
|
+
# puts("ASG: #{asg} #{asg.auto_scaling_group_name} (#{asg.desired_capacity})")
|
86
|
+
# asg.instances.each do |i|
|
87
|
+
# puts(" Instance #{i.instance_id}: #{i.health_status}")
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
instances_inservice = 0
|
91
|
+
desired_capacity = asg.desired_capacity
|
92
|
+
|
93
|
+
get_all_instances.each do |i|
|
94
|
+
next if i.nil? || i.state.code != Constants::INSTANCE_STATE_CODE_RUNNING
|
95
|
+
return false if i.state.name != Constants::INSTANCE_STATE_HEALTHY
|
96
|
+
|
97
|
+
instances_inservice += 1
|
98
|
+
end
|
99
|
+
|
100
|
+
return :misconfigured if desired_capacity < 1
|
101
|
+
return :recovering if instances_inservice < desired_capacity
|
102
|
+
return :scaling if instances_inservice > desired_capacity
|
103
|
+
|
104
|
+
:ready
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return true, if all instances within the ELB are inService
|
108
|
+
def wait_until_healthy_elb
|
109
|
+
raise ELBMisconfiguration if health_state_elb.eql?(:misconfigured)
|
110
|
+
|
111
|
+
sleep 60 while health_state_elb.eql?(:recovering) || health_state_elb.eql?(:scaling)
|
112
|
+
return true if health_state_elb.eql?(:ready)
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait_until_healthy_asg
|
116
|
+
raise ASGMisconfiguration if health_state_asg.eql?(:misconfigured)
|
117
|
+
|
118
|
+
sleep 60 while health_state_asg.eql?(:recovering) || health_state_asg.eql?(:scaling)
|
119
|
+
return true if health_state_asg.eql?(:ready)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# @return AutoScalingGroup by StackPrefix and Component tags.
|
125
|
+
def find_auto_scaling_group(asg_client)
|
126
|
+
autoscaling_groups = asg_client.describe_auto_scaling_groups(max_records: 50)
|
127
|
+
find_auto_scaling_group = find_auto_scaling_group_name(autoscaling_groups)
|
128
|
+
|
129
|
+
return find_auto_scaling_group unless find_auto_scaling_group.nil?
|
130
|
+
|
131
|
+
until autoscaling_groups.next_token.nil?
|
132
|
+
autoscaling_groups = asg_client.describe_auto_scaling_groups(max_records: 50, next_token: autoscaling_groups.next_token)
|
133
|
+
find_auto_scaling_group = find_auto_scaling_group_name(autoscaling_groups)
|
134
|
+
return find_auto_scaling_group unless find_auto_scaling_group.nil?
|
135
|
+
end
|
136
|
+
return nil if find_auto_scaling_group.nil?
|
137
|
+
end
|
138
|
+
|
139
|
+
def find_auto_scaling_group_name(autoscaling_groups)
|
140
|
+
autoscaling_groups.auto_scaling_groups.each do |autoscaling_group|
|
141
|
+
asg_matches_stack_prefix = false
|
142
|
+
asg_matches_component = false
|
143
|
+
tags = autoscaling_group.tags
|
144
|
+
tags.each do |tag|
|
145
|
+
if tag.key == 'StackPrefix' && tag.value == descriptor.stack_prefix
|
146
|
+
asg_matches_stack_prefix = true
|
147
|
+
break if asg_matches_component
|
148
|
+
|
149
|
+
next
|
150
|
+
end
|
151
|
+
if tag.key == 'Component' && tag.value == descriptor.ec2.component
|
152
|
+
asg_matches_component = true
|
153
|
+
break if asg_matches_stack_prefix
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return autoscaling_group if asg_matches_stack_prefix && asg_matches_component
|
157
|
+
end
|
158
|
+
nil
|
159
|
+
end
|
160
|
+
|
161
|
+
# @return ElasticLoadBalancer by StackPrefix and logical-id tags.
|
162
|
+
def find_elb(elb_client)
|
163
|
+
elbs = elb_client.describe_load_balancers.load_balancer_descriptions
|
164
|
+
elbs.each do |elb|
|
165
|
+
elb_matches_stack_prefix = false
|
166
|
+
elb_matches_logical_id = false
|
167
|
+
tag_descriptions = elb_client.describe_tags(load_balancer_names: [elb.load_balancer_name]).tag_descriptions
|
168
|
+
next if tag_descriptions.empty?
|
169
|
+
|
170
|
+
tags = tag_descriptions[0].tags
|
171
|
+
tags.each do |tag|
|
172
|
+
if tag.key == 'StackPrefix' && tag.value == descriptor.stack_prefix
|
173
|
+
elb_matches_stack_prefix = true
|
174
|
+
break if elb_matches_logical_id
|
175
|
+
|
176
|
+
next
|
177
|
+
end
|
178
|
+
if tag.key == 'aws:cloudformation:logical-id' && tag.value == descriptor.elb.id
|
179
|
+
elb_matches_logical_id = true
|
180
|
+
break if elb_matches_stack_prefix
|
181
|
+
end
|
182
|
+
end
|
183
|
+
return elb if elb_matches_stack_prefix && elb_matches_logical_id
|
184
|
+
end
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|