cfn_manage 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ require 'aws-sdk-docdb'
2
+ require 'cfn_manage/aws_credentials'
3
+
4
+ module CfnManage
5
+ module StartStopHandler
6
+ class DocumentDb
7
+
8
+ def initialize(cluster_id, options = {})
9
+ @cluster_id = cluster_id
10
+ credentials = CfnManage::AWSCredentials.get_session_credentials("startstopcluster_#{cluster_id}")
11
+ @docdb_client = Aws::DocDB::Client.new(retry_limit: 20)
12
+ if credentials != nil
13
+ @docdb_client = Aws::DocDB::Client.new(credentials: credentials, retry_limit: 20)
14
+ end
15
+ cluster = @docdb_client.describe_db_clusters({ db_cluster_identifier: @cluster_id })
16
+ @docdb_cluster = cluster.db_clusters.first
17
+ end
18
+
19
+ def start(configuration)
20
+ if @docdb_cluster.status == 'available'
21
+ $log.info("DocDB Cluster #{@cluster_id} is already in available state")
22
+ return
23
+ end
24
+
25
+ # start docdb cluster
26
+ if @docdb_cluster.status == 'stopped'
27
+ $log.info("Starting DocDB cluster #{@cluster_id}")
28
+ @docdb_client.start_db_cluster({ db_cluster_identifier: @cluster_id })
29
+ unless CfnManage.skip_wait?
30
+ # wait cluster to become available
31
+ $log.info("Waiting DocDB cluster to become available #{@cluster_id}")
32
+ wait('available')
33
+ end
34
+ else
35
+ $log.info("DocDB Cluster #{@cluster_id} is not in a stopped state. State: #{@docdb_cluster.status}")
36
+ end
37
+ end
38
+
39
+ def stop
40
+ if @docdb_cluster.status == 'stopped'
41
+ $log.info("DocDB Cluster #{@cluster_id} is already stopped")
42
+ return {}
43
+ end
44
+
45
+ if @docdb_cluster.status != 'available'
46
+ $log.info("DocDB Cluster #{@cluster_id} is not in a available state. State: #{@docdb_cluster.status}")
47
+ return {}
48
+ end
49
+ # stop docdb cluster and wait for it to be fully stopped
50
+ $log.info("Stopping DocDB cluster #{@cluster_id}")
51
+ @docdb_client.stop_db_cluster({ db_cluster_identifier: @cluster_id })
52
+ unless CfnManage.skip_wait?
53
+ $log.info("Waiting DocDB cluster to be stopped #{@cluster_id}")
54
+ wait('stopped')
55
+ end
56
+ return {}
57
+ end
58
+
59
+ def wait(completed_state)
60
+ # reached state must be steady, at least a minute.
61
+ state_count = 0
62
+ steady_count = 4
63
+ attempts = 0
64
+
65
+ until attempts == (max_attempts = 60*6) do
66
+ # Declare client and cluster variable a second time inside the loop so it re-evaluates each time.
67
+ docdb = @docdb_client.describe_db_clusters({ db_cluster_identifier: @cluster_id })
68
+ cluster = docdb.db_clusters.first
69
+ $log.info("DocDB Cluster #{cluster.db_cluster_identifier} state: #{cluster.status}, waiting for #{completed_state}")
70
+
71
+ if cluster.status == "#{completed_state}"
72
+ state_count = state_count + 1
73
+ $log.info("#{state_count}/#{steady_count}")
74
+ else
75
+ state_count = 0
76
+ end
77
+ break if state_count == steady_count
78
+ attempts = attempts + 1
79
+ sleep(15)
80
+ end
81
+
82
+ if attempts == max_attempts
83
+ $log.error("DocDB Cluster #{@cluster_id} did not enter #{completed_state} state, however continuing operations...")
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,42 @@
1
+ require 'aws-sdk-ec2'
2
+ require 'cfn_manage/aws_credentials'
3
+
4
+ module CfnManage
5
+ module StartStopHandler
6
+ class Ec2
7
+
8
+ def initialize(instance_id, options = {})
9
+ credentials = CfnManage::AWSCredentials.get_session_credentials("stoprun_#{instance_id}")
10
+ ec2_client = Aws::EC2::Client.new(credentials: credentials, retry_limit: 20)
11
+ @instance = Aws::EC2::Resource.new(client: ec2_client, retry_limit: 20).instance(instance_id)
12
+ @instance_id = instance_id
13
+ end
14
+
15
+ def start(configuration)
16
+ if %w(running).include?(@instance.state.name)
17
+ $log.info("Instance #{@instance_id} already running")
18
+ return
19
+ end
20
+ $log.info("Starting instance #{@instance_id}")
21
+ @instance.start()
22
+ end
23
+
24
+ def stop
25
+ if %w(stopped stopping).include?(@instance.state.name)
26
+ $log.info("Instance #{@instance_id} already stopping or stopped")
27
+ return
28
+ end
29
+ $log.info("Stopping instance #{@instance_id}")
30
+ @instance.stop()
31
+
32
+ # empty configuration for ec2 instances
33
+ return {}
34
+ end
35
+
36
+ def wait(wait_states=[])
37
+ $log.debug("Not waiting for EC2 instance #{@instance_id}")
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,207 @@
1
+ require 'aws-sdk-ecs'
2
+ require 'cfn_manage/aws_credentials'
3
+
4
+ module CfnManage
5
+ module StartStopHandler
6
+ class EcsCluster
7
+
8
+ def initialize(cluster_id, options = {})
9
+ @wait_state = options.has_key?(:wait_state) ? options[:wait_state] : CfnManage.ecs_wait_state
10
+ @skip_wait = options.has_key?(:skip_wait) ? CfnManage.true?(options[:skip_wait]) : CfnManage.skip_wait?
11
+ @wait_container_instances = options.has_key?(:wait_container_instances) ? CfnManage.true?(options[:wait_container_instances]) : CfnManage.ecs_wait_container_instances?
12
+ @ignore_missing_ecs_config = options.has_key?(:ignore_missing_ecs_config) ? CfnManage.true?(options[:ignore_missing_ecs_config]) : CfnManage.ignore_missing_ecs_config?
13
+
14
+ credentials = CfnManage::AWSCredentials.get_session_credentials("stoprun_#{cluster_id}")
15
+ @ecs_client = Aws::ECS::Client.new(credentials: credentials, retry_limit: 20)
16
+ @elb_client = Aws::ElasticLoadBalancingV2::Client.new(credentials: credentials, retry_limit: 20)
17
+ @services = []
18
+ @ecs_client.list_services(cluster: cluster_id, scheduling_strategy: 'REPLICA', max_results: 100).each do |results|
19
+ @services.push(*results.service_arns)
20
+ end
21
+ $log.info("Found #{@services.count} services in ECS cluster #{cluster_id}")
22
+ @cluster = cluster_id
23
+ end
24
+
25
+ def start(configuration)
26
+ if @wait_container_instances
27
+ wait_for_instances()
28
+ end
29
+
30
+ @services.each do |service_arn|
31
+
32
+ $log.info("Searching for ECS service #{service_arn} in cluster #{@cluster}")
33
+ service = @ecs_client.describe_services(services:[service_arn], cluster: @cluster).services.first
34
+
35
+ if service.desired_count != 0
36
+ $log.info("ECS service #{service.service_name} is already running")
37
+ next
38
+ end
39
+
40
+ if configuration.has_key?(service.service_name)
41
+ desired_count = configuration[service.service_name]['desired_count']
42
+ elsif CfnManage.ignore_missing_ecs_config?
43
+ $log.info("ECS service #{service.service_name} wasn't previosly stopped by cfn_manage. Option --ignore-missing-ecs-config set and setting desired count to 1")
44
+ desired_count = 1
45
+ else
46
+ $log.warn("ECS service #{service.service_name} wasn't previosly stopped by cfn_manage. Skipping ...")
47
+ next
48
+ end
49
+
50
+ $log.info("Starting ECS service #{service.service_name} with desired count of #{desired_count}")
51
+ @ecs_client.update_service({
52
+ desired_count: desired_count,
53
+ service: service_arn,
54
+ cluster: @cluster
55
+ })
56
+
57
+ end
58
+
59
+ if desired_count == 0
60
+ # skip wait if desired count is purposfully set to 0
61
+ $log.info("Desired capacity is 0, skipping wait for ecs service #{service.service_name}")
62
+ elsif !@skip_wait
63
+ @services.each do |service_arn|
64
+ wait(@wait_state,service_arn)
65
+ end
66
+ end
67
+ end
68
+
69
+ def stop
70
+ configuration = {}
71
+ @services.each do |service_arn|
72
+
73
+ $log.info("Searching for ECS service #{service_arn} in cluster #{@cluster}")
74
+ service = @ecs_client.describe_services(services:[service_arn], cluster: @cluster).services.first
75
+
76
+ if service.desired_count == 0
77
+ $log.info("ECS service #{service.service_name} is already stopped")
78
+ next
79
+ end
80
+
81
+ configuration[service.service_name] = { desired_count: service.desired_count }
82
+ $log.info("Stopping ECS service #{service.service_name}")
83
+ @ecs_client.update_service({
84
+ desired_count: 0,
85
+ service: service_arn,
86
+ cluster: @cluster
87
+ })
88
+
89
+ end
90
+
91
+ return configuration.empty? ? nil : configuration
92
+ end
93
+
94
+ def wait(type,service_arn=nil)
95
+
96
+ if service_arn.nil?
97
+ $log.warn("unable to wait for #{service_arn} service")
98
+ return
99
+ end
100
+
101
+ attempts = 0
102
+
103
+ until attempts == (max_attempts = 60*6) do
104
+
105
+ case type
106
+ when 'Running'
107
+ success = wait_till_running(service_arn)
108
+ when 'HealthyInTargetGroup'
109
+ success = wait_till_healthy_in_target_group(service_arn)
110
+ else
111
+ $log.warn("unknown ecs service wait type #{type}. skipping...")
112
+ break
113
+ end
114
+
115
+ if success
116
+ break
117
+ end
118
+
119
+ attempts = attempts + 1
120
+ sleep(15)
121
+ end
122
+
123
+ if attempts == max_attempts
124
+ $log.error("Failed to wait for ecs service with wait type #{type}")
125
+ end
126
+ end
127
+
128
+ def wait_for_instances
129
+
130
+ attempts = 0
131
+
132
+ until attempts == (max_attempts = 60*3) do
133
+
134
+ resp = @ecs_client.list_container_instances({
135
+ cluster: @cluster,
136
+ status: "ACTIVE"
137
+ })
138
+
139
+ if resp.container_instance_arns.any?
140
+ $log.info("A container instances has joined ecs cluster #{@cluster}")
141
+ break
142
+ end
143
+
144
+ attempts = attempts + 1
145
+ sleep(5)
146
+ end
147
+
148
+ if attempts == max_attempts
149
+ $log.error("Failed to wait for container instances to join ecs cluster #{@cluster}")
150
+ end
151
+ end
152
+
153
+ def wait_till_running(service_arn)
154
+ service_name = service_arn.split('/').last
155
+ service = @ecs_client.describe_services(services:[service_arn], cluster: @cluster).services.first
156
+
157
+ if service.running_count > 0
158
+ $log.info("ecs service #{service_name} has #{service.running_count} running tasks")
159
+ return true
160
+ end
161
+
162
+ $log.info("waiting for ecs service #{service_name} to reach a running state")
163
+ return false
164
+ end
165
+
166
+ def wait_till_healthy_in_target_group(service_arn)
167
+ service = @ecs_client.describe_services(services:[service_arn], cluster: @cluster).services.first
168
+ target_groups = service.load_balancers.collect { |lb| lb.target_group_arn }
169
+
170
+ if target_groups.empty?
171
+ # we want to skip here if the asg is not associated with any target groups
172
+ $log.info("ecs aervice #{service_arn} is not associated with any target groups")
173
+ return true
174
+ end
175
+
176
+ target_health = []
177
+ target_groups.each do |tg|
178
+ resp = @elb_client.describe_target_health({
179
+ target_group_arn: tg,
180
+ })
181
+ if resp.target_health_descriptions.empty?
182
+ # we need to wait until a ecs task has been placed into the target group
183
+ # before we can check it's healthy
184
+ $log.info("ECS service #{service_arn} hasn't been placed into target group #{tg.split('/')[1]} yet")
185
+ return false
186
+ end
187
+ target_health.push(*resp.target_health_descriptions)
188
+ end
189
+
190
+ state = target_health.collect {|tg| tg.target_health.state}
191
+
192
+ if state.all? 'healthy'
193
+ $log.info("All ecs tasks are in a healthy state in target groups #{target_groups.map {|tg| tg.split('/')[1] }}")
194
+ return true
195
+ end
196
+
197
+ unhealthy = target_health.select {|tg| tg.target_health.state != 'healthy'}
198
+ unhealthy.each do |tg|
199
+ $log.info("waiting for ecs task #{tg.target.id} to be healthy in target group. Current state is #{tg.target_health.state}")
200
+ end
201
+
202
+ return false
203
+ end
204
+
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,134 @@
1
+ require 'aws-sdk-rds'
2
+ require 'cfn_manage/aws_credentials'
3
+
4
+ module CfnManage
5
+ module StartStopHandler
6
+ class Rds
7
+
8
+ def initialize(instance_id, options = {})
9
+ @instance_id = instance_id
10
+ @excluded_engines = %w(aurora aurora-mysql aurora-postgresql) # RDS list of exluded engines that don't support RDS stop start
11
+ credentials = CfnManage::AWSCredentials.get_session_credentials("startstoprds_#{instance_id}")
12
+ @rds_client = Aws::RDS::Client.new(retry_limit: 20)
13
+ if credentials != nil
14
+ @rds_client = Aws::RDS::Client.new(credentials: credentials, retry_limit: 20)
15
+ end
16
+ rds = Aws::RDS::Resource.new(client: @rds_client)
17
+ @rds_instance = rds.db_instance(instance_id)
18
+
19
+ end
20
+
21
+ def start(configuration)
22
+ if @excluded_engines.include? @rds_instance.engine
23
+ $log.info("RDS Instance #{@instance_id} engine is #{@rds_instance.engine} and cannot be started by instance.")
24
+ return
25
+ end
26
+
27
+ if @rds_instance.db_instance_status == 'available'
28
+ $log.info("RDS Instance #{@instance_id} is already in available state")
29
+ end
30
+
31
+ # start rds instance
32
+ if @rds_instance.db_instance_status == 'stopped'
33
+ $log.info("Starting db instance #{@instance_id}")
34
+ @rds_client.start_db_instance({ db_instance_identifier: @instance_id })
35
+
36
+ # wait instance to become available
37
+ unless CfnManage.skip_wait?
38
+ $log.info("Waiting db instance to become available #{@instance_id}")
39
+ wait('available')
40
+ end
41
+ else
42
+ wait('available') unless CfnManage.skip_wait?
43
+ end
44
+
45
+ # convert rds instance to mutli-az if required
46
+ if configuration['is_multi_az']
47
+ $log.info("Converting to Multi-AZ instance after start (instance #{@instance_id})")
48
+ set_rds_instance_multi_az( true)
49
+ end unless configuration.nil?
50
+ end
51
+
52
+ def stop
53
+
54
+ configuration = {
55
+ is_multi_az: @rds_instance.multi_az
56
+ }
57
+ # RDS list of exluded engines that don't support RDS stop start
58
+ if @excluded_engines.include? @rds_instance.engine
59
+ $log.info("RDS Instance #{@instance_id} engine is #{@rds_instance.engine} and cannot be stoped by instance.")
60
+ return configuration
61
+ end
62
+
63
+ # check if available
64
+ if @rds_instance.db_instance_status != 'available'
65
+ $log.warn("RDS Instance #{@instance_id} not in available state, and thus can not be stopped")
66
+ $log.warn("RDS Instance #{@instance_id} state: #{@rds_instance.db_instance_status}")
67
+ return configuration
68
+ end
69
+
70
+ # check if already stopped
71
+ if @rds_instance.db_instance_status == 'stopped'
72
+ $log.info("RDS Instance #{@instance_id} is already stopped")
73
+ return configuration
74
+ end
75
+
76
+ #check if mutli-az RDS. if so, convert to single-az
77
+ if @rds_instance.multi_az
78
+ $log.info("Converting to Non-Multi-AZ instance before stop (instance #{@instance_id}")
79
+ set_rds_instance_multi_az(false)
80
+ end
81
+
82
+ # stop rds instance and wait for it to be fully stopped
83
+ $log.info("Stopping instance #{@instance_id}")
84
+ @rds_client.stop_db_instance({ db_instance_identifier: @instance_id })
85
+ unless CfnManage.skip_wait?
86
+ $log.info("Waiting db instance to be stopped #{@instance_id}")
87
+ wait('stopped')
88
+ end
89
+
90
+ return configuration
91
+ end
92
+
93
+ def set_rds_instance_multi_az(multi_az)
94
+ if @rds_instance.multi_az == multi_az
95
+ $log.info("Rds instance #{@rds_instance.db_instance_identifier} already multi-az=#{multi_az}")
96
+ return
97
+ end
98
+ @rds_instance.modify({ multi_az: multi_az, apply_immediately: true })
99
+ # allow half an hour for instance to be converted
100
+ wait('available')
101
+ end
102
+
103
+ def wait(completed_state)
104
+ # reached state must be steady, at least a minute. Modifying an instance to/from MultiAZ can't be shorter
105
+ # than 40 seconds, hence steady count is 4
106
+ state_count = 0
107
+ steady_count = 4
108
+ attempts = 0
109
+ rds = Aws::RDS::Resource.new(client: @rds_client)
110
+ until attempts == (max_attempts = 60*6) do
111
+ instance = rds.db_instance(@instance_id)
112
+ $log.info("Instance #{instance.db_instance_identifier} state: #{instance.db_instance_status}, waiting for #{completed_state}")
113
+
114
+ if instance.db_instance_status == "#{completed_state}"
115
+ state_count = state_count + 1
116
+ $log.info("#{state_count}/#{steady_count}")
117
+ else
118
+ state_count = 0
119
+ end
120
+ break if state_count == steady_count
121
+ attempts = attempts + 1
122
+ sleep(15)
123
+ end
124
+
125
+ if attempts == max_attempts
126
+ $log.error("RDS Database Instance #{@instance_id} did not enter #{state} state, however continuing operations...")
127
+ end
128
+ end
129
+
130
+ private :set_rds_instance_multi_az
131
+
132
+ end
133
+ end
134
+ end