summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSoumitra Khuntia <sk698p@att.com>2018-08-20 17:58:39 +0530
committerSmruti Soumitra Khuntia <sk698p@att.com>2018-09-25 17:54:59 +0530
commitf879e3a88db58c8d42c5987dd8d542b1e35e59c1 (patch)
tree8209eaffa9d95cbba7edc0b8eabdc63a4bc756f0
parent6697c0f23f17ecb3a9b885aa39d290bbe14f0e57 (diff)
Update node-labels through Kubernetes Provisioner
Blueprint: https://airshipit.readthedocs.io/projects/specs/en/latest/specs/approved/k8s_update_node_labels_workflow.html This commit adds: 1. A new task action ''relabel_nodes'' added to update nodes labels 2.A new Kubernetes driver added for Kubernetes cluster interaction through Promenade. Change-Id: I37c2d7bfda4966d907556036bc2b343df451994c
Notes
Notes (review): Code-Review+1: Dan Crank <dan.no@att.com> Code-Review+2: Bryan Strassner <bryan.strassner@gmail.com> Code-Review+1: Ahmad Mahmoudi <am495p@att.com> Code-Review+2: Scott Hussey <sthussey@att.com> Workflow+1: Scott Hussey <sthussey@att.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 25 Sep 2018 20:55:53 +0000 Reviewed-on: https://review.openstack.org/593609 Project: openstack/airship-drydock Branch: refs/heads/master
-rw-r--r--docs/source/_static/drydock.conf.sample9
-rw-r--r--docs/source/_static/policy.yaml.sample4
-rw-r--r--docs/source/task.rst4
-rw-r--r--python/drydock_provisioner/config.py13
-rw-r--r--python/drydock_provisioner/control/tasks.py25
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/__init__.py14
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/driver.py46
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/README.md4
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/__init__.py14
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/__init__.py14
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/k8s_node.py83
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/driver.py156
-rw-r--r--python/drydock_provisioner/drivers/kubernetes/promenade_driver/promenade_client.py296
-rw-r--r--python/drydock_provisioner/objects/fields.py16
-rw-r--r--python/drydock_provisioner/objects/node.py11
-rw-r--r--python/drydock_provisioner/orchestrator/actions/orchestrator.py60
-rw-r--r--python/drydock_provisioner/orchestrator/orchestrator.py11
-rw-r--r--python/drydock_provisioner/orchestrator/readme.md5
-rw-r--r--python/drydock_provisioner/policy.py6
-rw-r--r--python/tests/unit/test_k8sdriver_promenade_client.py194
20 files changed, 977 insertions, 8 deletions
diff --git a/docs/source/_static/drydock.conf.sample b/docs/source/_static/drydock.conf.sample
index 64f6fb8..d0fc93d 100644
--- a/docs/source/_static/drydock.conf.sample
+++ b/docs/source/_static/drydock.conf.sample
@@ -276,6 +276,9 @@
276# Logger name for Node driver logging (string value) 276# Logger name for Node driver logging (string value)
277#nodedriver_logger_name = ${global_logger_name}.nodedriver 277#nodedriver_logger_name = ${global_logger_name}.nodedriver
278 278
279# Logger name for Kubernetes driver logging (string value)
280#kubernetesdriver_logger_name = ${global_logger_name}.kubernetesdriver
281
279# Logger name for API server logging (string value) 282# Logger name for API server logging (string value)
280#control_logger_name = ${global_logger_name}.control 283#control_logger_name = ${global_logger_name}.control
281 284
@@ -350,6 +353,9 @@
350# Module path string of the Node driver to enable (string value) 353# Module path string of the Node driver to enable (string value)
351#node_driver = drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver 354#node_driver = drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver
352 355
356# Module path string of the Kubernetes driver to enable (string value)
357#kubernetes_driver = drydock_provisioner.drivers.kubernetes.promenade_driver.driver.PromenadeDriver
358
353# Module path string of the Network driver enable (string value) 359# Module path string of the Network driver enable (string value)
354#network_driver = <None> 360#network_driver = <None>
355 361
@@ -398,6 +404,9 @@
398# Timeout in minutes for deploying a node (integer value) 404# Timeout in minutes for deploying a node (integer value)
399#deploy_node = 45 405#deploy_node = 45
400 406
407# Timeout in minutes for relabeling a node (integer value)
408#relabel_node = 5
409
401# Timeout in minutes between deployment completion and the all boot actions 410# Timeout in minutes between deployment completion and the all boot actions
402# reporting status (integer value) 411# reporting status (integer value)
403#bootaction_final_status = 15 412#bootaction_final_status = 15
diff --git a/docs/source/_static/policy.yaml.sample b/docs/source/_static/policy.yaml.sample
index 65706bf..22b2365 100644
--- a/docs/source/_static/policy.yaml.sample
+++ b/docs/source/_static/policy.yaml.sample
@@ -38,6 +38,10 @@
38# POST /api/v1.0/tasks 38# POST /api/v1.0/tasks
39#"physical_provisioner:destroy_nodes": "role:admin" 39#"physical_provisioner:destroy_nodes": "role:admin"
40 40
41# Create relabel_nodes task
42# POST /api/v1.0/tasks
43#"physical_provisioner:relabel_nodes": "role:admin"
44
41# Read build data for a node 45# Read build data for a node
42# GET /api/v1.0/nodes/{nodename}/builddata 46# GET /api/v1.0/nodes/{nodename}/builddata
43#"physical_provisioner:read_build_data": "role:admin" 47#"physical_provisioner:read_build_data": "role:admin"
diff --git a/docs/source/task.rst b/docs/source/task.rst
index 3c08d2a..e648b37 100644
--- a/docs/source/task.rst
+++ b/docs/source/task.rst
@@ -14,7 +14,7 @@ Task Document Schema
14This document can be posted to the Drydock :ref:`tasks-api` to create a new task.:: 14This document can be posted to the Drydock :ref:`tasks-api` to create a new task.::
15 15
16 { 16 {
17 "action": "validate_design|verify_site|prepare_site|verify_node|prepare_node|deploy_node|destroy_node", 17 "action": "validate_design|verify_site|prepare_site|verify_node|prepare_node|deploy_node|destroy_node|relabel_nodes",
18 "design_ref": "http_uri|deckhand_uri|file_uri", 18 "design_ref": "http_uri|deckhand_uri|file_uri",
19 "node_filter": { 19 "node_filter": {
20 "filter_set_type": "intersection|union", 20 "filter_set_type": "intersection|union",
@@ -90,7 +90,7 @@ When querying the state of an existing task, the below document will be returned
90 "Kind": "Task", 90 "Kind": "Task",
91 "apiVersion": "v1.0", 91 "apiVersion": "v1.0",
92 "task_id": "uuid", 92 "task_id": "uuid",
93 "action": "validate_design|verify_site|prepare_site|verify_node|prepare_node|deploy_node|destroy_node", 93 "action": "validate_design|verify_site|prepare_site|verify_node|prepare_node|deploy_node|destroy_node|relabel_nodes",
94 "design_ref": "http_uri|deckhand_uri|file_uri", 94 "design_ref": "http_uri|deckhand_uri|file_uri",
95 "parent_task_id": "uuid", 95 "parent_task_id": "uuid",
96 "subtask_id_list": ["uuid","uuid",...], 96 "subtask_id_list": ["uuid","uuid",...],
diff --git a/python/drydock_provisioner/config.py b/python/drydock_provisioner/config.py
index e54e25c..a724876 100644
--- a/python/drydock_provisioner/config.py
+++ b/python/drydock_provisioner/config.py
@@ -82,6 +82,10 @@ class DrydockConfig(object):
82 default='${global_logger_name}.nodedriver', 82 default='${global_logger_name}.nodedriver',
83 help='Logger name for Node driver logging'), 83 help='Logger name for Node driver logging'),
84 cfg.StrOpt( 84 cfg.StrOpt(
85 'kubernetesdriver_logger_name',
86 default='${global_logger_name}.kubernetesdriver',
87 help='Logger name for Kubernetes driver logging'),
88 cfg.StrOpt(
85 'control_logger_name', 89 'control_logger_name',
86 default='${global_logger_name}.control', 90 default='${global_logger_name}.control',
87 help='Logger name for API server logging'), 91 help='Logger name for API server logging'),
@@ -166,6 +170,11 @@ class DrydockConfig(object):
166 default= 170 default=
167 'drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver', 171 'drydock_provisioner.drivers.node.maasdriver.driver.MaasNodeDriver',
168 help='Module path string of the Node driver to enable'), 172 help='Module path string of the Node driver to enable'),
173 cfg.StrOpt(
174 'kubernetes_driver',
175 default=
176 'drydock_provisioner.drivers.kubernetes.promenade_driver.driver.PromenadeDriver',
177 help='Module path string of the Kubernetes driver to enable'),
169 # TODO(sh8121att) Network driver not yet implemented 178 # TODO(sh8121att) Network driver not yet implemented
170 cfg.StrOpt( 179 cfg.StrOpt(
171 'network_driver', 180 'network_driver',
@@ -224,6 +233,10 @@ class DrydockConfig(object):
224 default=30, 233 default=30,
225 help='Timeout in minutes for releasing a node', 234 help='Timeout in minutes for releasing a node',
226 ), 235 ),
236 cfg.IntOpt(
237 'relabel_node',
238 default=5,
239 help='Timeout in minutes for relabeling a node'),
227 ] 240 ]
228 241
229 def __init__(self): 242 def __init__(self):
diff --git a/python/drydock_provisioner/control/tasks.py b/python/drydock_provisioner/control/tasks.py
index 700109e..1f70550 100644
--- a/python/drydock_provisioner/control/tasks.py
+++ b/python/drydock_provisioner/control/tasks.py
@@ -63,6 +63,7 @@ class TasksResource(StatefulResource):
63 'prepare_nodes': TasksResource.task_prepare_nodes, 63 'prepare_nodes': TasksResource.task_prepare_nodes,
64 'deploy_nodes': TasksResource.task_deploy_nodes, 64 'deploy_nodes': TasksResource.task_deploy_nodes,
65 'destroy_nodes': TasksResource.task_destroy_nodes, 65 'destroy_nodes': TasksResource.task_destroy_nodes,
66 'relabel_nodes': TasksResource.task_relabel_nodes,
66 } 67 }
67 68
68 try: 69 try:
@@ -253,6 +254,30 @@ class TasksResource(StatefulResource):
253 self.return_error( 254 self.return_error(
254 resp, falcon.HTTP_400, message=ex.msg, retry=False) 255 resp, falcon.HTTP_400, message=ex.msg, retry=False)
255 256
257 @policy.ApiEnforcer('physical_provisioner:relabel_nodes')
258 def task_relabel_nodes(self, req, resp, json_data):
259 """Create async task for relabel nodes."""
260 action = json_data.get('action', None)
261
262 if action != 'relabel_nodes':
263 self.error(
264 req.context,
265 "Task body ended up in wrong handler: action %s in task_relabel_nodes"
266 % action)
267 self.return_error(
268 resp, falcon.HTTP_500, message="Error", retry=False)
269
270 try:
271 task = self.create_task(json_data, req.context)
272 resp.body = json.dumps(task.to_dict())
273 resp.append_header('Location',
274 "/api/v1.0/tasks/%s" % str(task.task_id))
275 resp.status = falcon.HTTP_201
276 except errors.InvalidFormat as ex:
277 self.error(req.context, ex.msg)
278 self.return_error(
279 resp, falcon.HTTP_400, message=ex.msg, retry=False)
280
256 def create_task(self, task_body, req_context): 281 def create_task(self, task_body, req_context):
257 """General task creation. 282 """General task creation.
258 283
diff --git a/python/drydock_provisioner/drivers/kubernetes/__init__.py b/python/drydock_provisioner/drivers/kubernetes/__init__.py
new file mode 100644
index 0000000..9bf02db
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/__init__.py
@@ -0,0 +1,14 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Drivers for use for Kubernetes provisioner interaction."""
diff --git a/python/drydock_provisioner/drivers/kubernetes/driver.py b/python/drydock_provisioner/drivers/kubernetes/driver.py
new file mode 100644
index 0000000..fda1eb4
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/driver.py
@@ -0,0 +1,46 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Generic driver for Kubernetes Interaction."""
15
16import drydock_provisioner.objects.fields as hd_fields
17import drydock_provisioner.error as errors
18
19from drydock_provisioner.drivers.driver import ProviderDriver
20
21
22class KubernetesDriver(ProviderDriver):
23
24 driver_name = "Kubernetes_generic"
25 driver_key = "Kubernetes_generic"
26 driver_desc = "Generic Kubernetes Driver"
27
28 def __init__(self, **kwargs):
29 super(KubernetesDriver, self).__init__(**kwargs)
30
31 self.supported_actions = [
32 hd_fields.OrchestratorAction.RelabelNode,
33 ]
34
35 def execute_task(self, task_id):
36 task = self.state_manager.get_task(task_id)
37 task_action = task.action
38
39 if task_action in self.supported_actions:
40 task.success()
41 task.set_status(hd_fields.TaskStatus.Complete)
42 task.save()
43 return
44 else:
45 raise errors.DriverError("Unsupported action %s for driver %s" %
46 (task_action, self.driver_desc))
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/README.md b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/README.md
new file mode 100644
index 0000000..1208a29
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/README.md
@@ -0,0 +1,4 @@
1# Promenade Kubernetes Driver #
2
3This driver will handle the interaction with Promenade for
4any changes applied to Kubernetes cluster nodes.
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/__init__.py b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/__init__.py
new file mode 100644
index 0000000..621dd4d
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/__init__.py
@@ -0,0 +1,14 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Drivers for use for Promenade interaction."""
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/__init__.py b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/__init__.py
new file mode 100644
index 0000000..b92ce27
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/__init__.py
@@ -0,0 +1,14 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Kubernetes task driver action for Promenade interaction."""
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/k8s_node.py b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/k8s_node.py
new file mode 100644
index 0000000..359a679
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/actions/k8s_node.py
@@ -0,0 +1,83 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Task driver for Promenade interaction."""
15
16import logging
17
18import drydock_provisioner.error as errors
19import drydock_provisioner.config as config
20import drydock_provisioner.objects.fields as hd_fields
21from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
22
23
24class PromenadeAction(BaseAction):
25 def __init__(self, *args, prom_client=None):
26 super().__init__(*args)
27
28 self.promenade_client = prom_client
29
30 self.logger = logging.getLogger(
31 config.config_mgr.conf.logging.kubernetesdriver_logger_name)
32
33
34class RelabelNode(PromenadeAction):
35 """Action to relabel kubernetes node."""
36
37 def start(self):
38
39 self.task.set_status(hd_fields.TaskStatus.Running)
40 self.task.save()
41
42 try:
43 site_design = self._load_site_design()
44 except errors.OrchestratorError:
45 self.task.add_status_msg(
46 msg="Error loading site design.",
47 error=True,
48 ctx='NA',
49 ctx_type='NA')
50 self.task.set_status(hd_fields.TaskStatus.Complete)
51 self.task.failure()
52 self.task.save()
53 return
54
55 nodes = self.orchestrator.process_node_filter(self.task.node_filter,
56 site_design)
57
58 for n in nodes:
59 # Relabel node through Promenade
60 try:
61 self.logger.info("Relabeling node %s with node label data." % n.name)
62
63 labels_dict = n.get_node_labels()
64 msg = "Set labels %s for node %s" % (str(labels_dict), n.name)
65
66 self.task.add_status_msg(
67 msg=msg, error=False, ctx=n.name, ctx_type='node')
68
69 # Call promenade to invoke relabel node
70 self.promenade_client.relabel_node(n.get_id(), labels_dict)
71 self.task.success(focus=n.get_id())
72 except Exception as ex:
73 msg = "Error relabeling node %s with label data" % n.name
74 self.logger.warning(msg + ": " + str(ex))
75 self.task.failure(focus=n.get_id())
76 self.task.add_status_msg(
77 msg=msg, error=True, ctx=n.name, ctx_type='node')
78 continue
79
80 self.task.set_status(hd_fields.TaskStatus.Complete)
81 self.task.save()
82
83 return
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/driver.py b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/driver.py
new file mode 100644
index 0000000..876d5fa
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/driver.py
@@ -0,0 +1,156 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13"""Task driver for Promenade"""
14
15import logging
16import uuid
17import concurrent.futures
18
19from oslo_config import cfg
20
21import drydock_provisioner.error as errors
22import drydock_provisioner.objects.fields as hd_fields
23import drydock_provisioner.config as config
24from drydock_provisioner.drivers.kubernetes.driver import KubernetesDriver
25from drydock_provisioner.drivers.kubernetes.promenade_driver.promenade_client \
26 import PromenadeClient
27
28from .actions.k8s_node import RelabelNode
29
30
31class PromenadeDriver(KubernetesDriver):
32
33 driver_name = 'promenadedriver'
34 driver_key = 'promenadedriver'
35 driver_desc = 'Promenade Kubernetes Driver'
36
37 action_class_map = {
38 hd_fields.OrchestratorAction.RelabelNode:
39 RelabelNode,
40 }
41
42 def __init__(self, **kwargs):
43 super().__init__(**kwargs)
44
45 self.logger = logging.getLogger(
46 cfg.CONF.logging.kubernetesdriver_logger_name)
47
48 def execute_task(self, task_id):
49 # actions that should be threaded for execution
50 threaded_actions = [
51 hd_fields.OrchestratorAction.RelabelNode,
52 ]
53
54 action_timeouts = {
55 hd_fields.OrchestratorAction.RelabelNode:
56 config.config_mgr.conf.timeouts.relabel_node,
57 }
58
59 task = self.state_manager.get_task(task_id)
60
61 if task is None:
62 raise errors.DriverError("Invalid task %s" % (task_id))
63
64 if task.action not in self.supported_actions:
65 raise errors.DriverError("Driver %s doesn't support task action %s"
66 % (self.driver_desc, task.action))
67
68 task.set_status(hd_fields.TaskStatus.Running)
69 task.save()
70
71 if task.action in threaded_actions:
72 if task.retry > 0:
73 msg = "Retrying task %s on previous failed entities." % str(
74 task.get_id())
75 task.add_status_msg(
76 msg=msg,
77 error=False,
78 ctx=str(task.get_id()),
79 ctx_type='task')
80 target_nodes = self.orchestrator.get_target_nodes(
81 task, failures=True)
82 else:
83 target_nodes = self.orchestrator.get_target_nodes(task)
84
85 with concurrent.futures.ThreadPoolExecutor() as e:
86 subtask_futures = dict()
87 for n in target_nodes:
88 prom_client = PromenadeClient()
89 nf = self.orchestrator.create_nodefilter_from_nodelist([n])
90 subtask = self.orchestrator.create_task(
91 design_ref=task.design_ref,
92 action=task.action,
93 node_filter=nf,
94 retry=task.retry)
95 task.register_subtask(subtask)
96
97 action = self.action_class_map.get(task.action, None)(
98 subtask,
99 self.orchestrator,
100 self.state_manager,
101 prom_client=prom_client)
102 subtask_futures[subtask.get_id().bytes] = e.submit(
103 action.start)
104
105 timeout = action_timeouts.get(
106 task.action,
107 config.config_mgr.conf.timeouts.relabel_node)
108 finished, running = concurrent.futures.wait(
109 subtask_futures.values(), timeout=(timeout * 60))
110
111 for t, f in subtask_futures.items():
112 if not f.done():
113 task.add_status_msg(
114 "Subtask timed out before completing.",
115 error=True,
116 ctx=str(uuid.UUID(bytes=t)),
117 ctx_type='task')
118 task.failure()
119 else:
120 if f.exception():
121 msg = ("Subtask %s raised unexpected exception: %s"
122 % (str(uuid.UUID(bytes=t)), str(f.exception())))
123 self.logger.error(msg, exc_info=f.exception())
124 task.add_status_msg(
125 msg=msg,
126 error=True,
127 ctx=str(uuid.UUID(bytes=t)),
128 ctx_type='task')
129 task.failure()
130
131 task.bubble_results()
132 task.align_result()
133 else:
134 try:
135 prom_client = PromenadeClient()
136 action = self.action_class_map.get(task.action, None)(
137 task,
138 self.orchestrator,
139 self.state_manager,
140 prom_client=prom_client)
141 action.start()
142 except Exception as e:
143 msg = ("Subtask for action %s raised unexpected exception: %s"
144 % (task.action, str(e)))
145 self.logger.error(msg, exc_info=e)
146 task.add_status_msg(
147 msg=msg,
148 error=True,
149 ctx=str(task.get_id()),
150 ctx_type='task')
151 task.failure()
152
153 task.set_status(hd_fields.TaskStatus.Complete)
154 task.save()
155
156 return
diff --git a/python/drydock_provisioner/drivers/kubernetes/promenade_driver/promenade_client.py b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/promenade_client.py
new file mode 100644
index 0000000..386c992
--- /dev/null
+++ b/python/drydock_provisioner/drivers/kubernetes/promenade_driver/promenade_client.py
@@ -0,0 +1,296 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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"""Client for submitting authenticated requests to Promenade API."""
15
16import logging
17import requests
18from urllib.parse import urlparse
19
20from keystoneauth1 import exceptions as exc
21
22import drydock_provisioner.error as errors
23from drydock_provisioner.util import KeystoneUtils
24
25# TODO: Remove this local implementation of Promenade Session and client once
26# Promenade api client is available as part of Promenade project.
27class PromenadeSession(object):
28 """
29 A session to the Promenade API maintaining credentials and API options
30
31 :param string marker: (optional) external context marker
32 :param tuple timeout: (optional) a tuple of connect, read timeout values
33 to use as the default for invocations using this session. A single
34 value may also be supplied instead of a tuple to indicate only the
35 read timeout to use
36 """
37
38 def __init__(self,
39 scheme='http',
40 marker=None,
41 timeout=None):
42 self.logger = logging.getLogger(__name__)
43 self.__session = requests.Session()
44
45 self.set_auth()
46
47 self.marker = marker
48 self.__session.headers.update({'X-Context-Marker': marker})
49
50 self.prom_url = self._get_prom_url()
51 self.port = self.prom_url.port
52 self.host = self.prom_url.hostname
53 self.scheme = scheme
54
55 if self.port:
56 self.base_url = "%s://%s:%s/api/" % (self.scheme, self.host,
57 self.port)
58 else:
59 # assume default port for scheme
60 self.base_url = "%s://%s/api/" % (self.scheme, self.host)
61
62 self.default_timeout = self._calc_timeout_tuple((20, 30), timeout)
63
64 def set_auth(self):
65
66 auth_header = self._auth_gen()
67 self.__session.headers.update(auth_header)
68
69 def get(self, route, query=None, timeout=None):
70 """
71 Send a GET request to Promenade.
72
73 :param string route: The URL string following the hostname and API prefix
74 :param dict query: A dict of k, v pairs to add to the query string
75 :param timeout: A single or tuple value for connect, read timeout.
76 A single value indicates the read timeout only
77 :return: A requests.Response object
78 """
79 auth_refresh = False
80 while True:
81 url = self.base_url + route
82 self.logger.debug('GET ' + url)
83 self.logger.debug('Query Params: ' + str(query))
84 resp = self.__session.get(
85 url, params=query, timeout=self._timeout(timeout))
86
87 if resp.status_code == 401 and not auth_refresh:
88 self.set_auth()
89 auth_refresh = True
90 else:
91 break
92
93 return resp
94
95 def put(self, endpoint, query=None, body=None, data=None, timeout=None):
96 """
97 Send a PUT request to Promenade. If both body and data are specified,
98 body will be used.
99
100 :param string endpoint: The URL string following the hostname and API prefix
101 :param dict query: A dict of k, v parameters to add to the query string
102 :param string body: A string to use as the request body. Will be treated as raw
103 :param data: Something json.dumps(s) can serialize. Result will be used as the request body
104 :param timeout: A single or tuple value for connect, read timeout.
105 A single value indicates the read timeout only
106 :return: A requests.Response object
107 """
108 auth_refresh = False
109 url = self.base_url + endpoint
110 while True:
111 self.logger.debug('PUT ' + url)
112 self.logger.debug('Query Params: ' + str(query))
113 if body is not None:
114 self.logger.debug(
115 "Sending PUT with explicit body: \n%s" % body)
116 resp = self.__session.put(
117 self.base_url + endpoint,
118 params=query,
119 data=body,
120 timeout=self._timeout(timeout))
121 else:
122 self.logger.debug(
123 "Sending PUT with JSON body: \n%s" % str(data))
124 resp = self.__session.put(
125 self.base_url + endpoint,
126 params=query,
127 json=data,
128 timeout=self._timeout(timeout))
129 if resp.status_code == 401 and not auth_refresh:
130 self.set_auth()
131 auth_refresh = True
132 else:
133 break
134
135 return resp
136
137 def post(self, endpoint, query=None, body=None, data=None, timeout=None):
138 """
139 Send a POST request to Drydock. If both body and data are specified,
140 body will be used.
141
142 :param string endpoint: The URL string following the hostname and API prefix
143 :param dict query: A dict of k, v parameters to add to the query string
144 :param string body: A string to use as the request body. Will be treated as raw
145 :param data: Something json.dumps(s) can serialize. Result will be used as the request body
146 :param timeout: A single or tuple value for connect, read timeout.
147 A single value indicates the read timeout only
148 :return: A requests.Response object
149 """
150 auth_refresh = False
151 url = self.base_url + endpoint
152 while True:
153 self.logger.debug('POST ' + url)
154 self.logger.debug('Query Params: ' + str(query))
155 if body is not None:
156 self.logger.debug(
157 "Sending POST with explicit body: \n%s" % body)
158 resp = self.__session.post(
159 self.base_url + endpoint,
160 params=query,
161 data=body,
162 timeout=self._timeout(timeout))
163 else:
164 self.logger.debug(
165 "Sending POST with JSON body: \n%s" % str(data))
166 resp = self.__session.post(
167 self.base_url + endpoint,
168 params=query,
169 json=data,
170 timeout=self._timeout(timeout))
171 if resp.status_code == 401 and not auth_refresh:
172 self.set_auth()
173 auth_refresh = True
174 else:
175 break
176
177 return resp
178
179 def _timeout(self, timeout=None):
180 """Calculate the default timeouts for this session
181
182 :param timeout: A single or tuple value for connect, read timeout.
183 A single value indicates the read timeout only
184 :return: the tuple of the default timeouts used for this session
185 """
186 return self._calc_timeout_tuple(self.default_timeout, timeout)
187
188 def _calc_timeout_tuple(self, def_timeout, timeout=None):
189 """Calculate the default timeouts for this session
190
191 :param def_timeout: The default timeout tuple to be used if no specific
192 timeout value is supplied
193 :param timeout: A single or tuple value for connect, read timeout.
194 A single value indicates the read timeout only
195 :return: the tuple of the timeouts calculated
196 """
197 connect_timeout, read_timeout = def_timeout
198
199 try:
200 if isinstance(timeout, tuple):
201 if all(isinstance(v, int)
202 for v in timeout) and len(timeout) == 2:
203 connect_timeout, read_timeout = timeout
204 else:
205 raise ValueError("Tuple non-integer or wrong length")
206 elif isinstance(timeout, int):
207 read_timeout = timeout
208 elif timeout is not None:
209 raise ValueError("Non integer timeout value")
210 except ValueError:
211 self.logger.warn(
212 "Timeout value must be a tuple of integers or a "
213 "single integer. Proceeding with values of "
214 "(%s, %s)", connect_timeout, read_timeout)
215 return (connect_timeout, read_timeout)
216
217 def _get_ks_session(self):
218 # Get keystone session object
219
220 try:
221 ks_session = KeystoneUtils.get_session()
222 except exc.AuthorizationFailure as aferr:
223 self.logger.error(
224 'Could not authorize against Keystone: %s',
225 str(aferr))
226 raise errors.DriverError('Could not authorize against Keystone: %s',
227 str(aferr))
228
229 return ks_session
230
231 def _get_prom_url(self):
232 # Get promenade url from Keystone session object
233
234 ks_session = self._get_ks_session()
235
236 try:
237 prom_endpoint = ks_session.get_endpoint(
238 interface='internal',
239 service_type='kubernetesprovisioner')
240 except exc.EndpointNotFound:
241 self.logger.error("Could not find an internal interface"
242 " defined in Keystone for Promenade")
243
244 raise errors.DriverError("Could not find an internal interface"
245 " defined in Keystone for Promenade")
246
247 prom_url = urlparse(prom_endpoint)
248
249 return prom_url
250
251 def _auth_gen(self):
252 # Get auth token from Keystone session
253 token = self._get_ks_session().get_auth_headers().get('X-Auth-Token')
254 return [('X-Auth-Token', token)]
255
256
257class PromenadeClient(object):
258 """"
259 A client for the Promenade API
260 """
261
262 def __init__(self):
263 self.session = PromenadeSession()
264 self.logger = logging.getLogger(__name__)
265
266 def relabel_node(self, node_id, node_labels):
267 """ Relabel kubernetes node
268
269 :param string node_id: Node id for node to be relabeled.
270 :param dict node_labels: The dictionary representation of node labels
271 that needs be re-applied to the node.
272 :return: response
273 """
274
275 route = 'v1.0/node-labels/{}'.format(node_id)
276
277 self.logger.debug("promenade_client is calling %s API: body is %s" %
278 (route, str(node_labels)))
279
280 resp = self.session.put(route, data=node_labels)
281
282 self._check_response(resp)
283
284 return resp.json()
285
286 def _check_response(self, resp):
287 if resp.status_code == 401:
288 raise errors.ClientUnauthorizedError(
289 "Unauthorized access to %s, include valid token." % resp.url)
290 elif resp.status_code == 403:
291 raise errors.ClientForbiddenError(
292 "Forbidden access to %s" % resp.url)
293 elif not resp.ok:
294 raise errors.ClientError(
295 "Error - received %d: %s" % (resp.status_code, resp.text),
296 code=resp.status_code)
diff --git a/python/drydock_provisioner/objects/fields.py b/python/drydock_provisioner/objects/fields.py
index efde1b9..71c6d48 100644
--- a/python/drydock_provisioner/objects/fields.py
+++ b/python/drydock_provisioner/objects/fields.py
@@ -31,6 +31,7 @@ class OrchestratorAction(BaseDrydockEnum):
31 DeployNodes = 'deploy_nodes' 31 DeployNodes = 'deploy_nodes'
32 DestroyNodes = 'destroy_nodes' 32 DestroyNodes = 'destroy_nodes'
33 BootactionReport = 'bootaction_report' 33 BootactionReport = 'bootaction_report'
34 RelabelNodes = 'relabel_nodes'
34 35
35 # OOB driver actions 36 # OOB driver actions
36 ValidateOobServices = 'validate_oob_services' 37 ValidateOobServices = 'validate_oob_services'
@@ -64,14 +65,17 @@ class OrchestratorAction(BaseDrydockEnum):
64 ConfigurePortProvisioning = 'config_port_provisioning' 65 ConfigurePortProvisioning = 'config_port_provisioning'
65 ConfigurePortProduction = 'config_port_production' 66 ConfigurePortProduction = 'config_port_production'
66 67
68 # Kubernetes driver actions
69 RelabelNode = 'relabel_node'
70
67 ALL = (Noop, ValidateDesign, VerifySite, PrepareSite, VerifyNodes, 71 ALL = (Noop, ValidateDesign, VerifySite, PrepareSite, VerifyNodes,
68 PrepareNodes, DeployNodes, BootactionReport, DestroyNodes, 72 PrepareNodes, DeployNodes, BootactionReport, DestroyNodes,
69 ConfigNodePxe, SetNodeBoot, PowerOffNode, PowerOnNode, 73 RelabelNodes, ConfigNodePxe, SetNodeBoot, PowerOffNode,
70 PowerCycleNode, InterrogateOob, CreateNetworkTemplate, 74 PowerOnNode, PowerCycleNode, InterrogateOob, RelabelNode,
71 CreateStorageTemplate, CreateBootMedia, PrepareHardwareConfig, 75 CreateNetworkTemplate, CreateStorageTemplate, CreateBootMedia,
72 ConfigureHardware, InterrogateNode, ApplyNodeNetworking, 76 PrepareHardwareConfig, ConfigureHardware, InterrogateNode,
73 ApplyNodeStorage, ApplyNodePlatform, DeployNode, DestroyNode, 77 ApplyNodeNetworking, ApplyNodeStorage, ApplyNodePlatform,
74 ConfigureNodeProvisioner) 78 DeployNode, DestroyNode, ConfigureNodeProvisioner)
75 79
76 80
77class OrchestratorActionField(fields.BaseEnumField): 81class OrchestratorActionField(fields.BaseEnumField):
diff --git a/python/drydock_provisioner/objects/node.py b/python/drydock_provisioner/objects/node.py
index 57ce1c7..a1ad556 100644
--- a/python/drydock_provisioner/objects/node.py
+++ b/python/drydock_provisioner/objects/node.py
@@ -326,6 +326,17 @@ class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):
326 alias) 326 alias)
327 return alias 327 return alias
328 328
329 def get_node_labels(self):
330 """Get node labels.
331 """
332
333 labels_dict = {}
334 for k, v in self.owner_data.items():
335 labels_dict[k] = v
336 self.logger.debug("node labels data : %s." % str(labels_dict))
337 # TODO: Generate node labels
338
339 return labels_dict
329 340
330@base.DrydockObjectRegistry.register 341@base.DrydockObjectRegistry.register
331class BaremetalNodeList(base.DrydockObjectListBase, base.DrydockObject): 342class BaremetalNodeList(base.DrydockObjectListBase, base.DrydockObject):
diff --git a/python/drydock_provisioner/orchestrator/actions/orchestrator.py b/python/drydock_provisioner/orchestrator/actions/orchestrator.py
index 7064fa8..90c6647 100644
--- a/python/drydock_provisioner/orchestrator/actions/orchestrator.py
+++ b/python/drydock_provisioner/orchestrator/actions/orchestrator.py
@@ -1035,6 +1035,66 @@ class DeployNodes(BaseAction):
1035 return 1035 return
1036 1036
1037 1037
1038class RelabelNodes(BaseAction):
1039 """Action to relabel a node"""
1040
1041 def start(self):
1042 """Start executing this action."""
1043 self.task.set_status(hd_fields.TaskStatus.Running)
1044 self.task.save()
1045
1046 kubernetes_driver = self.orchestrator.enabled_drivers['kubernetes']
1047
1048 if kubernetes_driver is None:
1049 self.task.set_status(hd_fields.TaskStatus.Complete)
1050 self.task.add_status_msg(
1051 msg="No kubernetes driver is enabled, ending task.",
1052 error=True,
1053 ctx=str(self.task.get_id()),
1054 ctx_type='task')
1055 self.task.result.set_message("No KubernetesDriver enabled.")
1056 self.task.result.set_reason("Bad Configuration.")
1057 self.task.failure()
1058 self.task.save()
1059 return
1060
1061 target_nodes = self.orchestrator.get_target_nodes(self.task)
1062
1063 if not target_nodes:
1064 self.task.add_status_msg(
1065 msg="No nodes in scope, nothing to relabel.",
1066 error=False,
1067 ctx='NA',
1068 ctx_type='NA')
1069 self.task.success()
1070 self.task.set_status(hd_fields.TaskStatus.Complete)
1071 self.task.save()
1072 return
1073
1074 nf = self.orchestrator.create_nodefilter_from_nodelist(target_nodes)
1075
1076 relabel_node_task = self.orchestrator.create_task(
1077 design_ref=self.task.design_ref,
1078 action=hd_fields.OrchestratorAction.RelabelNode,
1079 node_filter=nf)
1080 self.task.register_subtask(relabel_node_task)
1081
1082 self.logger.info(
1083 "Starting kubernetes driver task %s to relabel nodes." %
1084 (relabel_node_task.get_id()))
1085 kubernetes_driver.execute_task(relabel_node_task.get_id())
1086
1087 relabel_node_task = self.state_manager.get_task(
1088 relabel_node_task.get_id())
1089
1090 self.task.bubble_results(
1091 action_filter=hd_fields.OrchestratorAction.RelabelNode)
1092 self.task.align_result()
1093
1094 self.task.set_status(hd_fields.TaskStatus.Complete)
1095 self.task.save()
1096
1097
1038class BootactionReport(BaseAction): 1098class BootactionReport(BaseAction):
1039 """Wait for nodes to report status of boot action.""" 1099 """Wait for nodes to report status of boot action."""
1040 1100
diff --git a/python/drydock_provisioner/orchestrator/orchestrator.py b/python/drydock_provisioner/orchestrator/orchestrator.py
index 7717d87..e28ed15 100644
--- a/python/drydock_provisioner/orchestrator/orchestrator.py
+++ b/python/drydock_provisioner/orchestrator/orchestrator.py
@@ -33,6 +33,7 @@ from .actions.orchestrator import PrepareSite
33from .actions.orchestrator import VerifyNodes 33from .actions.orchestrator import VerifyNodes
34from .actions.orchestrator import PrepareNodes 34from .actions.orchestrator import PrepareNodes
35from .actions.orchestrator import DeployNodes 35from .actions.orchestrator import DeployNodes
36from .actions.orchestrator import RelabelNodes
36from .actions.orchestrator import DestroyNodes 37from .actions.orchestrator import DestroyNodes
37from .validations.validator import Validator 38from .validations.validator import Validator
38 39
@@ -102,6 +103,15 @@ class Orchestrator(object):
102 self.enabled_drivers['network'] = network_driver_class( 103 self.enabled_drivers['network'] = network_driver_class(
103 state_manager=state_manager, orchestrator=self) 104 state_manager=state_manager, orchestrator=self)
104 105
106 kubernetes_driver_name = enabled_drivers.kubernetes_driver
107 if kubernetes_driver_name is not None:
108 m, c = kubernetes_driver_name.rsplit('.', 1)
109 kubernetes_driver_class = getattr(
110 importlib.import_module(m), c, None)
111 if kubernetes_driver_class is not None:
112 self.enabled_drivers['kubernetes'] = kubernetes_driver_class(
113 state_manager=state_manager, orchestrator=self)
114
105 def watch_for_tasks(self): 115 def watch_for_tasks(self):
106 """Start polling the database watching for Queued tasks to execute.""" 116 """Start polling the database watching for Queued tasks to execute."""
107 orch_task_actions = { 117 orch_task_actions = {
@@ -112,6 +122,7 @@ class Orchestrator(object):
112 hd_fields.OrchestratorAction.VerifyNodes: VerifyNodes, 122 hd_fields.OrchestratorAction.VerifyNodes: VerifyNodes,
113 hd_fields.OrchestratorAction.PrepareNodes: PrepareNodes, 123 hd_fields.OrchestratorAction.PrepareNodes: PrepareNodes,
114 hd_fields.OrchestratorAction.DeployNodes: DeployNodes, 124 hd_fields.OrchestratorAction.DeployNodes: DeployNodes,
125 hd_fields.OrchestratorAction.RelabelNodes: RelabelNodes,
115 hd_fields.OrchestratorAction.DestroyNodes: DestroyNodes, 126 hd_fields.OrchestratorAction.DestroyNodes: DestroyNodes,
116 } 127 }
117 128
diff --git a/python/drydock_provisioner/orchestrator/readme.md b/python/drydock_provisioner/orchestrator/readme.md
index 30c5f1f..84dd0bd 100644
--- a/python/drydock_provisioner/orchestrator/readme.md
+++ b/python/drydock_provisioner/orchestrator/readme.md
@@ -111,6 +111,11 @@ success
111 111
112Destroy current node configuration and rebootstrap from scratch 112Destroy current node configuration and rebootstrap from scratch
113 113
114### RelabelNode ###
115
116Relabel current Kubernetes cluster node through Kubernetes
117provisioner.
118
114## Integration with Drivers ## 119## Integration with Drivers ##
115 120
116Based on the requested task and the current known state of a node 121Based on the requested task and the current known state of a node
diff --git a/python/drydock_provisioner/policy.py b/python/drydock_provisioner/policy.py
index e1c0660..a4c182f 100644
--- a/python/drydock_provisioner/policy.py
+++ b/python/drydock_provisioner/policy.py
@@ -95,6 +95,12 @@ class DrydockPolicy(object):
95 'path': '/api/v1.0/tasks', 95 'path': '/api/v1.0/tasks',
96 'method': 'POST' 96 'method': 'POST'
97 }]), 97 }]),
98 policy.DocumentedRuleDefault('physical_provisioner:relabel_nodes',
99 'role:admin', 'Create relabel_nodes task',
100 [{
101 'path': '/api/v1.0/tasks',
102 'method': 'POST'
103 }]),
98 policy.DocumentedRuleDefault( 104 policy.DocumentedRuleDefault(
99 'physical_provisioner:read_build_data', 'role:admin', 105 'physical_provisioner:read_build_data', 'role:admin',
100 'Read build data for a node', 106 'Read build data for a node',
diff --git a/python/tests/unit/test_k8sdriver_promenade_client.py b/python/tests/unit/test_k8sdriver_promenade_client.py
new file mode 100644
index 0000000..088bd6c
--- /dev/null
+++ b/python/tests/unit/test_k8sdriver_promenade_client.py
@@ -0,0 +1,194 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
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.
14from unittest import mock
15from urllib.parse import urlparse
16
17import pytest
18import responses
19
20import drydock_provisioner.error as errors
21from drydock_provisioner.drivers.kubernetes.promenade_driver.promenade_client \
22 import PromenadeSession, PromenadeClient
23
24PROM_URL = urlparse('http://promhost:80/api/v1.0')
25PROM_HOST = 'promhost'
26
27
28@mock.patch(
29 'drydock_provisioner.drivers.kubernetes'
30 '.promenade_driver.promenade_client'
31 '.PromenadeSession._get_prom_url',
32 return_value=PROM_URL)
33@mock.patch(
34 'drydock_provisioner.drivers.kubernetes'
35 '.promenade_driver.promenade_client'
36 '.PromenadeSession.set_auth',
37 return_value=None)
38@responses.activate
39def test_put(patch1, patch2):
40 """
41 Test put functionality
42 """
43 responses.add(
44 responses.PUT,
45 'http://promhost:80/api/v1.0/node-label/n1',
46 body='{"key1":"label1"}',
47 status=200)
48
49 prom_session = PromenadeSession()
50 result = prom_session.put('v1.0/node-label/n1',
51 body='{"key1":"label1"}',
52 timeout=(60, 60))
53
54 assert PROM_HOST == prom_session.host
55 assert result.status_code == 200
56
57
58@mock.patch(
59 'drydock_provisioner.drivers.kubernetes'
60 '.promenade_driver.promenade_client'
61 '.PromenadeSession._get_prom_url',
62 return_value=PROM_URL)
63@mock.patch(
64 'drydock_provisioner.drivers.kubernetes'
65 '.promenade_driver.promenade_client'
66 '.PromenadeSession.set_auth',
67 return_value=None)
68@responses.activate
69def test_get(patch1, patch2):
70 """
71 Test get functionality
72 """
73 responses.add(
74 responses.GET,
75 'http://promhost:80/api/v1.0/node-label/n1',
76 status=200)
77
78 prom_session = PromenadeSession()
79 result = prom_session.get('v1.0/node-label/n1',
80 timeout=(60, 60))
81
82 assert result.status_code == 200
83
84
85@mock.patch(
86 'drydock_provisioner.drivers.kubernetes'
87 '.promenade_driver.promenade_client'
88 '.PromenadeSession._get_prom_url',
89 return_value=PROM_URL)
90@mock.patch(
91 'drydock_provisioner.drivers.kubernetes'
92 '.promenade_driver.promenade_client'
93 '.PromenadeSession.set_auth',
94 return_value=None)
95@responses.activate
96def test_post(patch1, patch2):
97 """
98 Test post functionality
99 """
100 responses.add(
101 responses.POST,
102 'http://promhost:80/api/v1.0/node-label/n1',
103 body='{"key1":"label1"}',
104 status=200)
105
106 prom_session = PromenadeSession()
107 result = prom_session.post('v1.0/node-label/n1',
108 body='{"key1":"label1"}',
109 timeout=(60, 60))
110
111 assert PROM_HOST == prom_session.host
112 assert result.status_code == 200
113
114
115@mock.patch(
116 'drydock_provisioner.drivers.kubernetes'
117 '.promenade_driver.promenade_client'
118 '.PromenadeSession._get_prom_url',
119 return_value=PROM_URL)
120@mock.patch(
121 'drydock_provisioner.drivers.kubernetes'
122 '.promenade_driver.promenade_client'
123 '.PromenadeSession.set_auth',
124 return_value=None)
125@responses.activate
126def test_relabel_node(patch1, patch2):
127 """
128 Test relabel node call from Promenade
129 Client
130 """
131 responses.add(
132 responses.PUT,
133 'http://promhost:80/api/v1.0/node-labels/n1',
134 body='{"key1":"label1"}',
135 status=200)
136
137 prom_client = PromenadeClient()
138
139 result = prom_client.relabel_node('n1', {"key1": "label1"})
140
141 assert result == {"key1": "label1"}
142
143
144@mock.patch(
145 'drydock_provisioner.drivers.kubernetes'
146 '.promenade_driver.promenade_client'
147 '.PromenadeSession._get_prom_url',
148 return_value=PROM_URL)
149@mock.patch(
150 'drydock_provisioner.drivers.kubernetes'
151 '.promenade_driver.promenade_client'
152 '.PromenadeSession.set_auth',
153 return_value=None)
154@responses.activate
155def test_relabel_node_403_status(patch1, patch2):
156 """
157 Test relabel node with 403 resp status
158 """
159 responses.add(
160 responses.PUT,
161 'http://promhost:80/api/v1.0/node-labels/n1',
162 body='{"key1":"label1"}',
163 status=403)
164
165 prom_client = PromenadeClient()
166
167 with pytest.raises(errors.ClientForbiddenError):
168 prom_client.relabel_node('n1', {"key1": "label1"})
169
170@mock.patch(
171 'drydock_provisioner.drivers.kubernetes'
172 '.promenade_driver.promenade_client'
173 '.PromenadeSession._get_prom_url',
174 return_value=PROM_URL)
175@mock.patch(
176 'drydock_provisioner.drivers.kubernetes'
177 '.promenade_driver.promenade_client'
178 '.PromenadeSession.set_auth',
179 return_value=None)
180@responses.activate
181def test_relabel_node_401_status(patch1, patch2):
182 """
183 Test relabel node with 401 resp status
184 """
185 responses.add(
186 responses.PUT,
187 'http://promhost:80/api/v1.0/node-labels/n1',
188 body='{"key1":"label1"}',
189 status=401)
190
191 prom_client = PromenadeClient()
192
193 with pytest.raises(errors.ClientUnauthorizedError):
194 prom_client.relabel_node('n1', {"key1": "label1"})