Repository: augerai/a2ml
Issue: Auger. cluster update

if Auger cluster parameters changed, call project update:
PATCH/api​/v1​/projects​/{id} worker_type_id

Also do not harcode worker type id, match them by name:
GET /api/v1/worker_types

Changed files:
a2ml/api/auger/impl/experiment.py, a2ml/api/auger/impl/mparts/deploy.py, tests/tasks_queue/test_tasks_api_auger.py, a2ml/cmdl/commands/cmd_deploy.py, tests/auger/test_dataset.py, a2ml/api/auger/project.py, a2ml/api/auger/impl/dataset.py, a2ml/api/auger/impl/cloud/project.py, tests/auger/test_model.py, a2ml/cmdl/commands/cmd_model.py

diff --git a/a2ml/api/auger/impl/cloud/project.py b/a2ml/api/auger/impl/cloud/project.py
index 48e622b..fc540a6 100644
--- a/a2ml/api/auger/impl/cloud/project.py
+++ b/a2ml/api/auger/impl/cloud/project.py
@@ -22,12 +22,39 @@ class AugerProjectApi(AugerBaseApi):
             'name': self.object_name, 'organization_id': self.parent_api.oid})
 
     def start(self):
-        self._ensure_object_id()
+        project_properties = self.properties()
+        if project_properties.get('status') != 'running':
+            self.ctx.log('Starting Project...')
+            self._do_start(project_properties)
+            self.ctx.log('Started Project %s' % self.object_name)
+        # else:
+        #     self.ctx.log('Project is already running...')
+
+        self._update_cluster_settings()
+
+    def _update_cluster_settings(self):
         project_properties = self.properties()
 
+        cluster_id = project_properties.get('cluster_id')
+        cluster_api = AugerClusterApi(self.ctx, self, cluster_id)
+        cluster_settings = cluster_api.get_cluster_settings(self.ctx)
+
+        update_properties = {}
+        props_to_update = ['worker_type_id', 'workers_count', 'kubernetes_stack']
+        for prop in props_to_update:
+            if project_properties.get(prop, cluster_settings.get(prop)) != cluster_settings.get(prop):
+                update_properties[prop] = cluster_settings.get(prop)
+
+        if update_properties:
+            self.ctx.log('Update project cluster: %s' % update_properties)
+
+            update_properties['id'] = self.object_id
+            self._call_update(update_properties, progress=['undeployed', 'deployed', 'scaling', 'zero_scaled', 'deploying'])
+                
+    def _do_start(self, project_properties):
+        self._ensure_object_id()
         status = project_properties.get('status')
-        if status == 'running':
-            return project_properties
+
         project_status = ['deployed', 'deploying']
         if status in project_status:
             return self.wait_for_status(project_status)
diff --git a/a2ml/api/auger/impl/dataset.py b/a2ml/api/auger/impl/dataset.py
index 7c7f0dc..6a86af1 100644
--- a/a2ml/api/auger/impl/dataset.py
+++ b/a2ml/api/auger/impl/dataset.py
@@ -17,9 +17,7 @@ class DataSet(AugerDataSetApi):
         data_source_file, local_data_source = \\
             AugerDataSetApi.verify(data_source_file, self.ctx.config.path)
 
-        if not self.project.is_running():
-            self.ctx.log('Starting Project to process request...')
-            self.project.start()
+        self.project.start()
 
         super().create(data_source_file, self.object_name, local_data_source=local_data_source)
         return self
@@ -31,9 +29,7 @@ class DataSet(AugerDataSetApi):
         data_source_file, local_data_source = \\
             AugerDataSetApi.verify(data_source_file, self.ctx.config.path)
 
-        if not self.project.is_running():
-            self.ctx.log('Starting Project to process request...')
-            self.project.start()
+        self.project.start()
 
         return super().do_upload_file(data_source_file, self.object_name, local_data_source=local_data_source)
 
@@ -41,8 +37,6 @@ class DataSet(AugerDataSetApi):
         if path_to_download is None:
             raise AugerException('Please specify path to download...')
 
-        if not self.project.is_running():
-            self.ctx.log('Starting Project to process request...')
-            self.project.start()
+        self.project.start()
 
         return super().download(path_to_download)
diff --git a/a2ml/api/auger/impl/experiment.py b/a2ml/api/auger/impl/experiment.py
index 6b1fc22..a49585a 100644
--- a/a2ml/api/auger/impl/experiment.py
+++ b/a2ml/api/auger/impl/experiment.py
@@ -37,9 +37,7 @@ class Experiment(AugerExperimentApi):
                 self.ctx.log('Current experiment setup with different DataSet. '
                     'Will create new Experimet...')
 
-        if not self.dataset.project.is_running():
-            self.ctx.log('Starting Project to process request...')
-            self.dataset.project.start()
+        self.dataset.project.start()
 
         if (self.object_name is None) or (not self.is_exists):
             self.create(self.dataset.name)
diff --git a/a2ml/api/auger/impl/mparts/deploy.py b/a2ml/api/auger/impl/mparts/deploy.py
index 1e36875..fe9cba4 100644
--- a/a2ml/api/auger/impl/mparts/deploy.py
+++ b/a2ml/api/auger/impl/mparts/deploy.py
@@ -24,7 +24,7 @@ class ModelDeploy(object):
     def deploy_model_in_cloud(self, model_id, review):
         self.ctx.log('Deploying model %s' % model_id)
 
-        self._start_project()
+        self.project.start()
 
         pipeline_properties = AugerPipelineApi(
             self.ctx, None).create(model_id, review)
@@ -40,7 +40,7 @@ class ModelDeploy(object):
         if not is_loaded:
             self.ctx.log('Downloading model %s' % model_id)
 
-            self._start_project()
+            self.project.start()
 
             pipeline_file_api = AugerPipelineFileApi(self.ctx, None)
             pipeline_file_properties = pipeline_file_api.create(model_id)
@@ -62,11 +62,6 @@ class ModelDeploy(object):
         model_name = os.path.join(model_path, 'model-%s.zip' % model_id)
         return fsclient.is_file_exists(model_name), model_path, model_name
 
-    def _start_project(self):
-        if not self.project.is_running():
-            self.ctx.log('Starting Project to process request...')
-            self.project.start()
-
     def _docker_pull_image(self):
         cluster_settings = AugerClusterApi.get_cluster_settings(self.ctx)
         docker_tag = cluster_settings.get('kubernetes_stack')
diff --git a/a2ml/api/auger/project.py b/a2ml/api/auger/project.py
index 0671216..494bf71 100644
--- a/a2ml/api/auger/project.py
+++ b/a2ml/api/auger/project.py
@@ -48,12 +48,7 @@ class AugerProject(object):
     @authenticated
     def start(self, name):
         old_name, name, project = self._setup_op(name)
-        if not project.is_running():
-            self.ctx.log('Starting Project...')
-            project.start()
-            self.ctx.log('Started Project %s' % name)
-        else:
-            self.ctx.log('Project is already running...')
+        project.start()
         return {'running': name}
 
     @error_handler
diff --git a/a2ml/cmdl/commands/cmd_deploy.py b/a2ml/cmdl/commands/cmd_deploy.py
index 52bf92d..025e15d 100644
--- a/a2ml/cmdl/commands/cmd_deploy.py
+++ b/a2ml/cmdl/commands/cmd_deploy.py
@@ -9,10 +9,10 @@ from a2ml.api.utils.context import pass_context
 @click.argument('model-id', required=True, type=click.STRING)
 @click.option('--locally', is_flag=True, default=False,
     help='Download and deploy trained model locally.')
-@click.option('--review', is_flag=True, default=False,
-    help='Should model support review based on actual data.')
+@click.option('--no-review', is_flag=True, default=False,
+    help='Do not support model review based on actual data.')
 @pass_context
-def cmdl(ctx, provider, model_id, locally, review):
+def cmdl(ctx, provider, model_id, locally, no_review):
     """Deploy trained model."""
     ctx.setup_logger(format='')
-    A2ML(ctx, provider).deploy(model_id, locally, review)
+    A2ML(ctx, provider).deploy(model_id, locally, not no_review)
diff --git a/a2ml/cmdl/commands/cmd_model.py b/a2ml/cmdl/commands/cmd_model.py
index 417bc52..840ff16 100644
--- a/a2ml/cmdl/commands/cmd_model.py
+++ b/a2ml/cmdl/commands/cmd_model.py
@@ -13,14 +13,14 @@ def cmdl(ctx):
 @click.argument('model-id', required=True, type=click.STRING)
 @click.option('--locally', is_flag=True, default=False,
     help='Download and deploy trained model locally.')
-@click.option('--review', is_flag=True, default=False,
-    help='Should model support review based on actual data.')
+@click.option('--no-review', is_flag=True, default=False,
+    help='Do not support model review based on actual data.')
 @click.option('--provider', '-p', type=click.Choice(['auger','azure']), required=False,
     help='Cloud AutoML Provider.')
 @pass_context
-def deploy(ctx, provider, model_id, locally, review):
+def deploy(ctx, provider, model_id, locally, no_review):
     """Deploy trained model."""
-    A2MLModel(ctx, provider).deploy(model_id, locally, review)
+    A2MLModel(ctx, provider).deploy(model_id, locally, not no_review)
 
 @click.command('predict', short_help='Predict with deployed model.')
 @click.argument('filename', required=True, type=click.STRING)
diff --git a/tests/auger/test_dataset.py b/tests/auger/test_dataset.py
index f40d13f..4f787f0 100644
--- a/tests/auger/test_dataset.py
+++ b/tests/auger/test_dataset.py
@@ -36,7 +36,7 @@ class TestDataSet():
 
         interceptor(PAYLOAD, monkeypatch)
         object_status_chain(['processing', 'processed'], monkeypatch)
-        monkeypatch.setattr(AugerProjectApi, 'is_running', lambda x: True)
+        monkeypatch.setattr(AugerProjectApi, 'start', lambda self: None)
         monkeypatch.setattr('a2ml.api.auger.impl.cloud.dataset.AugerDataSetApi._upload_to_cloud', lambda *args: 's3://iris.csv')
 
         result = AugerDataset(ctx).create(source='iris.csv')
diff --git a/tests/auger/test_model.py b/tests/auger/test_model.py
index 18f1745..f193b30 100644
--- a/tests/auger/test_model.py
+++ b/tests/auger/test_model.py
@@ -15,7 +15,7 @@ class TestModel():
         interceptor(PAYLOAD, monkeypatch)
         object_status_chain(['undeployed', 'deployed', 'deploying', 'running'], monkeypatch)
         monkeypatch.setattr('a2ml.api.auger.impl.mparts.deploy.ModelDeploy._docker_pull_image', lambda self: 'experimental')
-        monkeypatch.setattr('a2ml.api.auger.impl.mparts.deploy.ModelDeploy._start_project', lambda self: None)
+        monkeypatch.setattr('a2ml.api.auger.impl.cloud.project.AugerProjectApi.start', lambda self: None)
         os.remove('models/model-87C81FE615DE46D.zip')
         # FIXME: let AugerPipelineFileApi do it's work
         monkeypatch.setattr('a2ml.api.auger.impl.cloud.pipeline_file.AugerPipelineFileApi.create', lambda self, model_id: {'signed_s3_model_path': 'None'})
diff --git a/tests/tasks_queue/test_tasks_api_auger.py b/tests/tasks_queue/test_tasks_api_auger.py
index 2107d3a..19220fe 100644
--- a/tests/tasks_queue/test_tasks_api_auger.py
+++ b/tests/tasks_queue/test_tasks_api_auger.py
@@ -32,7 +32,9 @@ class TestTasksApiAuger(BaseTest):
         self.assert_result(res, True, {'created': 'new-project-name'})
 
     @vcr.use_cassette('auger/import/valid.yaml')
-    def test_import_valid(self):
+    def test_import_valid(self, monkeypatch):
+        monkeypatch.setattr('a2ml.api.auger.impl.cloud.project.AugerProjectApi.start', lambda self: None)
+
         params = self.params('auger')
         res = import_data_task.apply(params).result
         self.assert_result(res, True, {'created': 'iris-13.csv'})
@@ -53,7 +55,9 @@ class TestTasksApiAuger(BaseTest):
             'status': 'completed', 'provider_status': 'completed', 'trials_count': 19})
 
     @vcr.use_cassette('auger/deploy/valid.yaml')
-    def test_deploy_valid(self):
+    def test_deploy_valid(self, monkeypatch):
+        monkeypatch.setattr('a2ml.api.auger.impl.cloud.project.AugerProjectApi.start', lambda self: None)
+
         params = self.params('auger', '390955D2AB984D7')
         res = deploy_task.apply(params).result
         self.assert_result(res, True, {'model_id': '390955D2AB984D7'}, no_provider_in_result=True)
@@ -102,7 +106,9 @@ class TestTasksApiAuger(BaseTest):
         self.assert_result(res, True, {'experiments': ANY})
 
     @vcr.use_cassette('auger/predict/valid.yaml')
-    def test_predict_success(self):
+    def test_predict_success(self, monkeypatch):
+        monkeypatch.setattr('a2ml.api.auger.impl.cloud.project.AugerProjectApi.start', lambda self: None)
+
         params = self.params(
             'auger',
             's3://sample-bucket/workspace/projects/a2ml-app/files/iris_for_predict.csv',
@@ -115,7 +121,9 @@ class TestTasksApiAuger(BaseTest):
         self.assert_result(res, True, {'predicted': ANY}, no_provider_in_result=True)
 
     @vcr.use_cassette('auger/predict/invalid_model.yaml')
-    def test_predict_failure_model_status(self):
+    def test_predict_failure_model_status(self, monkeypatch):
+        monkeypatch.setattr('a2ml.api.auger.impl.cloud.project.AugerProjectApi.start', lambda self: None)
+        
         params = self.params(
             'auger',
             's3://sample-bucket/workspace/projects/a2ml-app/files/iris_for_predict.csv',