Repository: zalando-stups/senza

Issue:
Make Elastigroup's On Demand not optional
The current implementation allows users not to specify the `InstanceType` attribute which would later be translated to the Elastigroups `ondemand` attribute. [This attribute is mandatory according to the API](https://api.spotinst.com/elastigroup/amazon-web-services-2/create/#compute.instanceTypes.ondemand) and [Spotint's recommendations](https://github.com/zalando-stups/senza/pull/516#pullrequestreview-136726224). The stack would not be created.

The fallbackToOd should always be set to True given that `ondemand` is mandatory.

Comments:

Patch:
diff --git a/spotinst/components/elastigroup.py b/spotinst/components/elastigroup.py
index aa916e9..dca20f4 100644
--- a/spotinst/components/elastigroup.py
+++ b/spotinst/components/elastigroup.py
@@ -20,7 +20,12 @@ from spotinst import MissingSpotinstAccount
 
 SPOTINST_LAMBDA_FORMATION_ARN = 'arn:aws:lambda:{}:178579023202:function:spotinst-cloudformation'
 SPOTINST_API_URL = 'https://api.spotinst.io'
-ELASTIGROUP_DEFAULT_STRATEGY = {"risk": 100, "availabilityVsCost": "balanced", "utilizeReservedInstances": True}
+ELASTIGROUP_DEFAULT_STRATEGY = {
+    "risk": 100,
+    "availabilityVsCost": "balanced",
+    "utilizeReservedInstances": True,
+    "fallbackToOd": True,
+}
 ELASTIGROUP_DEFAULT_PRODUCT = "Linux/UNIX"
 
 
@@ -432,20 +437,16 @@ def extract_instance_types(configuration, elastigroup_config):
     are no SpotAlternatives the Elastigroup will have the same ondemand type as spot alternative
     If there's already a compute.instanceTypes config it will be left untouched
     """
-    elastigroup_config = ensure_keys(ensure_keys(elastigroup_config, "strategy"), "compute")
+    elastigroup_config = ensure_keys(elastigroup_config, "compute")
     compute_config = elastigroup_config["compute"]
-    instance_type = configuration.pop("InstanceType", None)
+
+    if "InstanceType" not in configuration:
+        raise click.UsageError("You need to specify the InstanceType attribute to be able to use Elastigroups")
+    instance_type = configuration.pop("InstanceType")
     spot_alternatives = configuration.pop("SpotAlternatives", None)
     if "instanceTypes" not in compute_config:
-        if not (instance_type or spot_alternatives):
-            raise click.UsageError("You have to specify one of InstanceType or SpotAlternatives")
         instance_types = {}
-        strategy = elastigroup_config["strategy"]
-        if instance_type:
-            instance_types.update({"ondemand": instance_type})
-            strategy.update({"fallbackToOd": True})
-        else:
-            strategy.update({"fallbackToOd": False})
+        instance_types.update({"ondemand": instance_type})
         if spot_alternatives:
             instance_types.update({"spot": spot_alternatives})
         else:


Test patch:
diff --git a/tests/test_elastigroup.py b/tests/test_elastigroup.py
index f77ccc1..0fcb9e4 100644
--- a/tests/test_elastigroup.py
+++ b/tests/test_elastigroup.py
@@ -598,9 +598,11 @@ def test_extract_security_group_ids(monkeypatch):
             assert test_case["expected_sgs"] == got["compute"]["launchSpecification"].get("securityGroupIds")
 
 
-def test_missing_instance_types():
+def test_missing_instance_type():
     with pytest.raises(click.UsageError):
         extract_instance_types({}, {})
+    with pytest.raises(click.UsageError):
+        extract_instance_types({"SpotAlternatives": ["foo", "bar", "baz"]}, {})
 
 
 def test_extract_instance_types():
@@ -608,20 +610,12 @@ def test_extract_instance_types():
         {  # minimum accepted behavior, on demand instance type from typical Senza
             "input": {"InstanceType": "foo"},
             "given_config": {},
-            "expected_config": {"compute": {"instanceTypes": {"ondemand": "foo", "spot": ["foo"]}},
-                                "strategy": {"fallbackToOd": True}},
+            "expected_config": {"compute": {"instanceTypes": {"ondemand": "foo", "spot": ["foo"]}}},
         },
         {  # both on demand instance type from typical Senza and spot alternatives specified
             "input": {"InstanceType": "foo", "SpotAlternatives": ["bar", "baz"]},
             "given_config": {},
-            "expected_config": {"compute": {"instanceTypes": {"ondemand": "foo", "spot": ["bar", "baz"]}},
-                                "strategy": {"fallbackToOd": True}},
-        },
-        {  # only spot alternatives specified
-            "input": {"SpotAlternatives": ["foo", "bar"]},
-            "given_config": {},
-            "expected_config": {"compute": {"instanceTypes": {"spot": ["foo", "bar"]}},
-                                "strategy": {"fallbackToOd": False}},
+            "expected_config": {"compute": {"instanceTypes": {"ondemand": "foo", "spot": ["bar", "baz"]}}},
         },
     ]
     for test_case in test_cases: