1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5
6# pylint: disable=line-too-long,too-many-lines
7
8import os
9import time
10
11from azure.cli.core.util import get_file_json
12from azure.core.exceptions import HttpResponseError
13from azure.mgmt.servicefabric.models import (ApplicationTypeResource,
14                                             ApplicationTypeVersionResource,
15                                             ApplicationResource,
16                                             ApplicationUpgradePolicy,
17                                             ArmRollingUpgradeMonitoringPolicy,
18                                             ArmApplicationHealthPolicy,
19                                             ArmServiceTypeHealthPolicy)
20from azure.cli.command_modules.servicefabric._arm_deployment_utils import validate_and_deploy_arm_template
21
22from knack.log import get_logger
23
24logger = get_logger(__name__)
25
26
27def create_app(client,
28               resource_group_name,
29               cluster_name,
30               application_type_name,
31               application_type_version,
32               application_name,
33               package_url=None,
34               application_parameters=None,
35               minimum_nodes=None,
36               maximum_nodes=None):
37    if package_url is not None:
38        create_app_type_version(client, resource_group_name, cluster_name, application_type_name, application_type_version, package_url)
39
40    try:
41        apps = client.applications.list(resource_group_name, cluster_name)
42        for app in apps.value:
43            if app.name.lower() == application_name.lower():
44                logger.info("Application '%s' already exists", application_name)
45                return app
46
47        appResource = ApplicationResource(type_name=application_type_name,
48                                          type_version=application_type_version,
49                                          minimum_nodes=minimum_nodes,
50                                          maximum_nodes=maximum_nodes,
51                                          parameters=application_parameters)
52        appResource.name = application_name
53        app = client.applications.begin_create_or_update(resource_group_name, cluster_name, application_name, appResource).result()
54        return app
55    except HttpResponseError as ex:
56        logger.error("HttpResponseError: %s", ex)
57        raise
58
59
60def update_app(client,
61               resource_group_name,
62               cluster_name,
63               application_name,
64               application_type_version=None,
65               application_parameters=None,
66               minimum_nodes=None,
67               maximum_nodes=None,
68               force_restart=False,
69               upgrade_replica_set_check_timeout=None,
70               failure_action=None,
71               health_check_retry_timeout=None,
72               health_check_wait_duration=None,
73               health_check_stable_duration=None,
74               upgrade_domain_timeout=None,
75               upgrade_timeout=None,
76               consider_warning_as_error=False,
77               default_service_type_max_percent_unhealthy_partitions_per_service=None,
78               default_service_type_max_percent_unhealthy_replicas_per_partition=None,
79               default_service_type_max_percent_unhealthy_services=None,
80               max_percent_unhealthy_deployed_applications=None,
81               service_type_health_policy_map=None):
82    try:
83        currentApp = client.applications.get(resource_group_name, cluster_name, application_name)
84        appResource = currentApp
85        # TODO: change to patch once the fix is deployed in the rp
86        # appResourceUpdate: ApplicationResourceUpdate = ApplicationResourceUpdate()
87
88        if application_type_version:
89            appResource.type_version = application_type_version
90        if application_parameters:
91            appResource.parameters.update(application_parameters)
92        if minimum_nodes is not None:
93            appResource.minimum_nodes = minimum_nodes
94        if maximum_nodes is not None:
95            appResource.maximum_nodes = maximum_nodes
96
97        appResource.upgrade_policy = _set_uprade_policy(currentApp.upgrade_policy,
98                                                        force_restart,
99                                                        upgrade_replica_set_check_timeout,
100                                                        failure_action,
101                                                        health_check_retry_timeout,
102                                                        health_check_wait_duration,
103                                                        health_check_stable_duration,
104                                                        upgrade_domain_timeout,
105                                                        upgrade_timeout,
106                                                        consider_warning_as_error,
107                                                        default_service_type_max_percent_unhealthy_partitions_per_service,
108                                                        default_service_type_max_percent_unhealthy_replicas_per_partition,
109                                                        default_service_type_max_percent_unhealthy_services,
110                                                        max_percent_unhealthy_deployed_applications,
111                                                        service_type_health_policy_map)
112
113        # TODO: change to patch once the fix is deployed in the rp
114        # client.applications.update(resource_group_name, cluster_name, application_name, appResourceUpdate)
115        return client.applications.begin_create_or_update(resource_group_name, cluster_name, application_name, appResource).result()
116    except HttpResponseError as ex:
117        logger.error("HttpResponseError: %s", ex)
118        raise
119
120
121def create_app_type(client, resource_group_name, cluster_name, application_type_name):
122    try:
123        appTypes = client.application_types.list(resource_group_name, cluster_name)
124        for appType in appTypes.value:
125            if appType.name.lower() == application_type_name.lower():
126                logger.info("Application type '%s' already exists", application_type_name)
127                return appType
128
129        appTypeResource = ApplicationTypeResource()
130        logger.info("Creating application type '%s'", application_type_name)
131        return client.application_types.create_or_update(resource_group_name, cluster_name, application_type_name, appTypeResource)
132    except HttpResponseError as ex:
133        logger.error("HttpResponseError: %s", ex)
134        raise
135
136
137def create_app_type_version(client,
138                            resource_group_name,
139                            cluster_name,
140                            application_type_name,
141                            version,
142                            package_url):
143    create_app_type(client, resource_group_name, cluster_name, application_type_name)
144    try:
145        appTypeVerions = client.application_type_versions.list(resource_group_name, cluster_name, application_type_name)
146        for appTypeVerion in appTypeVerions.value:
147            if appTypeVerion.name.lower() == version.lower():
148                logger.info("Application type version '%s' already exists", version)
149                return appTypeVerion
150
151        appTypeVersionResource = ApplicationTypeVersionResource(app_package_url=package_url)
152        logger.info("Creating application type version %s:%s", application_type_name, version)
153        return client.application_type_versions.begin_create_or_update(resource_group_name,
154                                                                       cluster_name,
155                                                                       application_type_name,
156                                                                       version,
157                                                                       appTypeVersionResource).result()
158    except HttpResponseError as ex:
159        logger.error("HttpResponseError: %s", ex)
160        raise
161
162
163def create_service(cmd,
164                   client,
165                   resource_group_name,
166                   cluster_name,
167                   application_name,
168                   service_name,
169                   service_type,
170                   state,
171                   instance_count=None,
172                   target_replica_set_size=None,
173                   min_replica_set_size=None,
174                   default_move_cost=None,
175                   partition_scheme='singleton'):
176    parameter_file, template_file = _get_template_file_and_parameters_file()
177    template = get_file_json(template_file)
178    parameters = get_file_json(parameter_file)['parameters']
179
180    # set params
181    _set_parameters(parameters, "clusterName", cluster_name)
182    _set_parameters(parameters, "applicationName", application_name)
183    _set_parameters(parameters, "serviceName", service_name)
184
185    _set_service_parameters(template, parameters, "serviceTypeName", service_type, "string")
186
187    if partition_scheme == 'singleton':
188        _set_service_parameters(template, parameters, "partitionDescription", {"partitionScheme": "Singleton"}, "object")
189    elif partition_scheme == 'uniformInt64':
190        _set_service_parameters(template, parameters, "partitionDescription", {"partitionScheme": "UniformInt64Range"}, "object")
191    elif partition_scheme == 'named':
192        _set_service_parameters(template, parameters, "partitionDescription", {"partitionScheme": "Named"}, "object")
193
194    if state == 'stateless':
195        _set_service_parameters(template, parameters, "instanceCount", int(instance_count), "int")
196    else:
197        _set_service_parameters(template, parameters, "targetReplicaSetSize", int(target_replica_set_size), "int")
198        _set_service_parameters(template, parameters, "minReplicaSetSize", int(min_replica_set_size), "int")
199
200    if default_move_cost:
201        _set_service_parameters(template, parameters, "defaultMoveCost", default_move_cost, "string")
202
203    validate_and_deploy_arm_template(cmd, resource_group_name, template, parameters)
204
205    return client.services.get(resource_group_name, cluster_name, application_name, service_name)
206
207
208def _get_template_file_and_parameters_file():
209    script_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
210    template_parameter_folder = os.path.join('template', 'service')
211    parameter_file = os.path.join(
212        script_dir, template_parameter_folder, 'parameter.json')
213    template_file = os.path.join(
214        script_dir, template_parameter_folder, 'template.json')
215    return parameter_file, template_file
216
217
218def _set_service_parameters(template, parameters, name, value, param_type):
219    tempalte_parameters = template['parameters']
220    if name not in tempalte_parameters:
221        tempalte_parameters[name] = {}
222    tempalte_parameters[name]["type"] = param_type
223    tempalte_resources_properties = template['resources'][0]['properties']
224    tempalte_resources_properties[name] = "[parameters('{}')]".format(name)
225    _set_parameters(parameters, name, value)
226
227
228def _set_parameters(parameters, name, value):
229    if name not in parameters:
230        parameters[name] = {}
231    parameters[name]["value"] = value
232
233
234def _set_uprade_policy(current_upgrade_policy,
235                       force_restart,
236                       upgrade_replica_set_check_timeout,
237                       failure_action,
238                       health_check_retry_timeout,
239                       health_check_wait_duration,
240                       health_check_stable_duration,
241                       upgrade_domain_timeout,
242                       upgrade_timeout,
243                       consider_warning_as_error,
244                       default_service_type_max_percent_unhealthy_partitions_per_service,
245                       default_service_type_max_percent_unhealthy_replicas_per_partition,
246                       default_max_percent_service_type_unhealthy_services,
247                       max_percent_unhealthy_deployed_applications,
248                       service_type_health_policy_map):
249    if current_upgrade_policy is None:
250        current_upgrade_policy = ApplicationUpgradePolicy()
251
252    if force_restart:
253        current_upgrade_policy.force_restart = force_restart
254    if upgrade_replica_set_check_timeout is not None:
255        current_upgrade_policy.upgrade_replica_set_check_timeout = time.strftime('%H:%M:%S', time.gmtime(upgrade_replica_set_check_timeout))
256
257    # RollingUpgradeMonitoringPolicy
258    if current_upgrade_policy.rolling_upgrade_monitoring_policy is None:
259        # initialize with defaults
260        current_upgrade_policy.rolling_upgrade_monitoring_policy \
261            = ArmRollingUpgradeMonitoringPolicy(failure_action='Manual',
262                                                health_check_stable_duration=time.strftime('%H:%M:%S', time.gmtime(120)),
263                                                health_check_retry_timeout=time.strftime('%H:%M:%S', time.gmtime(600)),
264                                                health_check_wait_duration=time.strftime('%H:%M:%S', time.gmtime(0)),
265                                                upgrade_timeout=time.strftime('%H:%M:%S', time.gmtime(86399)),
266                                                upgrade_domain_timeout=time.strftime('%H:%M:%S', time.gmtime(86399)))
267
268    if failure_action:
269        current_upgrade_policy.rolling_upgrade_monitoring_policy.failure_action = failure_action
270    if health_check_stable_duration is not None:
271        current_upgrade_policy.rolling_upgrade_monitoring_policy.health_check_stable_duration = time.strftime('%H:%M:%S', time.gmtime(health_check_stable_duration))
272    if health_check_retry_timeout is not None:
273        current_upgrade_policy.rolling_upgrade_monitoring_policy.health_check_retry_timeout = time.strftime('%H:%M:%S', time.gmtime(health_check_retry_timeout))
274    if health_check_wait_duration is not None:
275        current_upgrade_policy.rolling_upgrade_monitoring_policy.health_check_wait_duration = time.strftime('%H:%M:%S', time.gmtime(health_check_wait_duration))
276    if upgrade_timeout is not None:
277        current_upgrade_policy.rolling_upgrade_monitoring_policy.upgrade_timeout = time.strftime('%H:%M:%S', time.gmtime(upgrade_timeout))
278    if upgrade_domain_timeout is not None:
279        current_upgrade_policy.rolling_upgrade_monitoring_policy.upgrade_domain_timeout = time.strftime('%H:%M:%S', time.gmtime(upgrade_domain_timeout))
280
281    # ApplicationHealthPolicy
282    if current_upgrade_policy.application_health_policy is None:
283        current_upgrade_policy.application_health_policy = ArmApplicationHealthPolicy()
284
285    if consider_warning_as_error:
286        current_upgrade_policy.application_health_policy.consider_warning_as_error = True
287
288    if current_upgrade_policy.application_health_policy.default_service_type_health_policy is None:
289        current_upgrade_policy.application_health_policy.default_service_type_health_policy = ArmServiceTypeHealthPolicy(
290            max_percent_unhealthy_partitions_per_service=default_service_type_max_percent_unhealthy_partitions_per_service,
291            max_percent_unhealthy_replicas_per_partition=default_service_type_max_percent_unhealthy_replicas_per_partition,
292            max_percent_unhealthy_services=default_max_percent_service_type_unhealthy_services)
293    else:
294        if default_service_type_max_percent_unhealthy_partitions_per_service:
295            current_upgrade_policy.application_health_policy.default_service_type_health_policy .max_percent_unhealthy_partitions_per_service \
296                = default_service_type_max_percent_unhealthy_partitions_per_service
297        if default_service_type_max_percent_unhealthy_replicas_per_partition:
298            current_upgrade_policy.application_health_policy.default_service_type_health_policy.max_percent_unhealthy_replicas_per_partition \
299                = default_service_type_max_percent_unhealthy_replicas_per_partition
300        if default_max_percent_service_type_unhealthy_services:
301            current_upgrade_policy.application_health_policy.default_service_type_health_policy.max_percent_unhealthy_partitions_per_service \
302                = default_max_percent_service_type_unhealthy_services
303
304    if max_percent_unhealthy_deployed_applications:
305        current_upgrade_policy.ApplicationHealthPolicy.max_percent_unhealthy_deployed_applications \
306            = max_percent_unhealthy_deployed_applications
307
308    if service_type_health_policy_map:
309        current_upgrade_policy.application_health_policy.service_type_health_policy_map = service_type_health_policy_map
310    return current_upgrade_policy
311