Fix bug 1542 to allow juju to add Azure AKS
[osm/N2VC.git] / n2vc / k8s_conn.py
1 ##
2 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3 # This file is part of OSM
4 # All Rights Reserved.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 # implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 # For those usages not covered by the Apache License, Version 2.0 please
20 # contact with: nfvlabs@tid.es
21 ##
22
23 import abc
24 import asyncio
25 import time
26
27 from n2vc.loggable import Loggable
28
29
30 class K8sConnector(abc.ABC, Loggable):
31 """
32 ####################################################################################
33 ################################### P U B L I C ####################################
34 ####################################################################################
35 """
36
37 @staticmethod
38 def generate_kdu_instance_name(**kwargs):
39 raise NotImplementedError("Method not implemented")
40
41 def __init__(self, db: object, log: object = None, on_update_db=None):
42 """
43
44 :param db: database object to write current operation status
45 :param log: logger for tracing
46 :param on_update_db: callback called when k8s connector updates database
47 """
48
49 # parent class
50 Loggable.__init__(self, log=log, log_to_console=True, prefix="\nK8S")
51
52 # self.log.info('Initializing generic K8S connector')
53
54 # the database and update callback
55 self.db = db
56 self.on_update_db = on_update_db
57
58 # self.log.info('K8S generic connector initialized')
59
60 @abc.abstractmethod
61 async def init_env(
62 self, k8s_creds: str, namespace: str = "kube-system", reuse_cluster_uuid=None
63 ) -> (str, bool):
64 """
65 It prepares a given K8s cluster environment to run Charts or juju Bundles on
66 both sides:
67 client (OSM)
68 server (Tiller/Charm)
69
70 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
71 '.kube/config'
72 :param namespace: optional namespace to be used for the K8s engine (helm
73 tiller, juju). By default, 'kube-system' will be used
74 :param reuse_cluster_uuid: existing cluster uuid for reuse
75 :return: uuid of the K8s cluster and True if connector has installed some
76 software in the cluster (on error, an exception will be raised)
77 """
78
79 @abc.abstractmethod
80 async def repo_add(
81 self, cluster_uuid: str, name: str, url: str, repo_type: str = "chart"
82 ):
83 """
84 Add a new repository to OSM database
85
86 :param cluster_uuid: the cluster
87 :param name: name for the repo in OSM
88 :param url: URL of the repo
89 :param repo_type: either "chart" or "bundle"
90 :return: True if successful
91 """
92
93 @abc.abstractmethod
94 async def repo_list(self, cluster_uuid: str):
95 """
96 Get the list of registered repositories
97
98 :param cluster_uuid: the cluster
99 :return: list of registered repositories: [ (name, url) .... ]
100 """
101
102 @abc.abstractmethod
103 async def repo_remove(self, cluster_uuid: str, name: str):
104 """
105 Remove a repository from OSM
106
107 :param name: repo name in OSM
108 :param cluster_uuid: the cluster
109 :return: True if successful
110 """
111
112 @abc.abstractmethod
113 async def synchronize_repos(self, cluster_uuid: str, name: str):
114 """
115 Synchronizes the list of repositories created in the cluster with
116 the repositories added by the NBI
117
118 :param cluster_uuid: the cluster
119 :return: List of repositories deleted from the cluster and dictionary with
120 repos added
121 """
122
123 @abc.abstractmethod
124 async def reset(
125 self, cluster_uuid: str, force: bool = False, uninstall_sw: bool = False
126 ) -> bool:
127 """
128 Uninstalls Tiller/Charm from a known K8s cluster and removes it from the list
129 of known K8s clusters. Intended to be used e.g. when the NS instance is deleted.
130
131 :param cluster_uuid: UUID of a K8s cluster known by OSM.
132 :param force: force deletion, even in case there are deployed releases
133 :param uninstall_sw: flag to indicate that sw uninstallation from software is
134 needed
135 :return: str: kdu_instance generated by helm
136 """
137
138 @abc.abstractmethod
139 async def install(
140 self,
141 cluster_uuid: str,
142 kdu_model: str,
143 kdu_instance: str,
144 atomic: bool = True,
145 timeout: float = 300,
146 params: dict = None,
147 db_dict: dict = None,
148 kdu_name: str = None,
149 namespace: str = None,
150 ):
151 """
152 Deploys of a new KDU instance. It would implicitly rely on the `install` call
153 to deploy the Chart/Bundle properly parametrized (in practice, this call would
154 happen before any _initial-config-primitive_of the VNF is called).
155
156 :param cluster_uuid: UUID of a K8s cluster known by OSM
157 :param kdu_model: chart/bundle:version reference (string), which can be either
158 of these options:
159 - a name of chart/bundle available via the repos known by OSM
160 - a path to a packaged chart/bundle
161 - a path to an unpacked chart/bundle directory or a URL
162 :param kdu_instance: Kdu instance name
163 :param atomic: If set, installation process purges chart/bundle on fail, also
164 will wait until all the K8s objects are active
165 :param timeout: Time in seconds to wait for the install of the chart/bundle
166 (defaults to Helm default timeout: 300s)
167 :param params: dictionary of key-value pairs for instantiation parameters
168 (overriding default values)
169 :param dict db_dict: where to write into database when the status changes.
170 It contains a dict with {collection: <str>, filter: {},
171 path: <str>},
172 e.g. {collection: "nsrs", filter:
173 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
174 :param kdu_name: Name of the KDU instance to be installed
175 :param namespace: K8s namespace to use for the KDU instance
176 :return: True if successful
177 """
178
179 @abc.abstractmethod
180 async def upgrade(
181 self,
182 cluster_uuid: str,
183 kdu_instance: str,
184 kdu_model: str = None,
185 atomic: bool = True,
186 timeout: float = 300,
187 params: dict = None,
188 db_dict: dict = None,
189 ):
190 """
191 Upgrades an existing KDU instance. It would implicitly use the `upgrade` call
192 over an existing Chart/Bundle. It can be used both to upgrade the chart or to
193 reconfigure it. This would be exposed as Day-2 primitive.
194
195 :param cluster_uuid: UUID of a K8s cluster known by OSM
196 :param kdu_instance: unique name for the KDU instance to be updated
197 :param kdu_model: new chart/bundle:version reference
198 :param atomic: rollback in case of fail and wait for pods and services are
199 available
200 :param timeout: Time in seconds to wait for the install of the chart/bundle
201 (defaults to Helm default timeout: 300s)
202 :param params: new dictionary of key-value pairs for instantiation parameters
203 :param dict db_dict: where to write into database when the status changes.
204 It contains a dict with {collection: <str>, filter: {},
205 path: <str>},
206 e.g. {collection: "nsrs", filter:
207 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
208 :return: reference to the new revision number of the KDU instance
209 """
210
211 @abc.abstractmethod
212 async def scale(
213 self,
214 kdu_instance: str,
215 scale: int,
216 resource_name: str,
217 total_timeout: float = 1800,
218 **kwargs,
219 ) -> bool:
220 """
221 Scales an application in KDU instance.
222
223 :param: kdu_instance str: KDU instance name
224 :param: scale int: Scale to which to set this application
225 :param: resource_name str: Resource name (Application name)
226 :param: timeout float: The time, in seconds, to wait for the install
227 to finish
228 :param kwargs: Additional parameters
229
230 :return: If successful, returns True
231 """
232
233 @abc.abstractmethod
234 async def get_scale_count(
235 self,
236 resource_name: str,
237 kdu_instance: str,
238 **kwargs,
239 ) -> int:
240 """
241 Get an application scale count.
242
243 :param: resource_name str: Resource name (Application name)
244 :param: kdu_instance str: KDU instance name
245 :param kwargs: Additional parameters
246
247 :return: Return application instance count
248 """
249
250 @abc.abstractmethod
251 async def rollback(
252 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
253 ):
254 """
255 Rolls back a previous update of a KDU instance. It would implicitly use the
256 `rollback` call. It can be used both to rollback from a Chart/Bundle version
257 update or from a reconfiguration. This would be exposed as Day-2 primitive.
258
259 :param cluster_uuid: UUID of a K8s cluster known by OSM
260 :param kdu_instance: unique name for the KDU instance
261 :param revision: revision to which revert changes. If omitted, it will revert
262 the last update only
263 :param dict db_dict: where to write into database when the status changes.
264 It contains a dict with {collection: <str>, filter: {},
265 path: <str>},
266 e.g. {collection: "nsrs", filter:
267 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
268 :return:If successful, reference to the current active revision of the KDU
269 instance after the rollback
270 """
271
272 @abc.abstractmethod
273 async def uninstall(self, cluster_uuid: str, kdu_instance: str):
274 """
275 Removes an existing KDU instance. It would implicitly use the `delete` call
276 (this call would happen after all _terminate-config-primitive_ of the VNF are
277 invoked).
278
279 :param cluster_uuid: UUID of a K8s cluster known by OSM
280 :param kdu_instance: unique name for the KDU instance to be deleted
281 :return: True if successful
282 """
283
284 @abc.abstractmethod
285 async def exec_primitive(
286 self,
287 cluster_uuid: str = None,
288 kdu_instance: str = None,
289 primitive_name: str = None,
290 timeout: float = 300,
291 params: dict = None,
292 db_dict: dict = None,
293 ) -> str:
294 """Exec primitive (Juju action)
295
296 :param cluster_uuid str: The UUID of the cluster
297 :param kdu_instance str: The unique name of the KDU instance
298 :param primitive_name: Name of action that will be executed
299 :param timeout: Timeout for action execution
300 :param params: Dictionary of all the parameters needed for the action
301 :db_dict: Dictionary for any additional data
302
303 :return: Returns the output of the action
304 """
305
306 @abc.abstractmethod
307 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
308 """
309 These calls will retrieve from the Chart/Bundle:
310
311 - The list of configurable values and their defaults (e.g. in Charts,
312 it would retrieve the contents of `values.yaml`).
313 - If available, any embedded help file (e.g. `readme.md`) embedded in the
314 Chart/Bundle.
315
316 :param kdu_model: chart/bundle reference
317 :param repo_url: optional, reposotory URL (None if tar.gz, URl in other cases,
318 even stable URL)
319 :return:
320
321 If successful, it will return the available parameters and their default values
322 as provided by the backend.
323 """
324
325 @abc.abstractmethod
326 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
327 """
328
329 :param kdu_model: chart/bundle reference
330 :param repo_url: optional, reposotory URL (None if tar.gz, URl in other cases,
331 even stable URL)
332 :return: If successful, it will return the contents of the 'readme.md'
333 """
334
335 @abc.abstractmethod
336 async def status_kdu(self, cluster_uuid: str, kdu_instance: str) -> str:
337 """
338 This call would retrieve tha current state of a given KDU instance. It would be
339 would allow to retrieve the _composition_ (i.e. K8s objects) and _specific
340 values_ of the configuration parameters applied to a given instance. This call
341 would be based on the `status` call.
342
343 :param cluster_uuid: UUID of a K8s cluster known by OSM
344 :param kdu_instance: unique name for the KDU instance
345 :return: If successful, it will return the following vector of arguments:
346 - K8s `namespace` in the cluster where the KDU lives
347 - `state` of the KDU instance. It can be:
348 - UNKNOWN
349 - DEPLOYED
350 - DELETED
351 - SUPERSEDED
352 - FAILED or
353 - DELETING
354 - List of `resources` (objects) that this release consists of, sorted by kind,
355 and the status of those resources
356 - Last `deployment_time`.
357
358 """
359
360 @abc.abstractmethod
361 async def get_services(
362 self, cluster_uuid: str, kdu_instance: str, namespace: str
363 ) -> list:
364 """
365 Returns a list of services defined for the specified kdu instance.
366
367 :param cluster_uuid: UUID of a K8s cluster known by OSM
368 :param kdu_instance: unique name for the KDU instance
369 :param namespace: K8s namespace used by the KDU instance
370 :return: If successful, it will return a list of services, Each service
371 can have the following data:
372 - `name` of the service
373 - `type` type of service in the k8 cluster
374 - `ports` List of ports offered by the service, for each port includes at least
375 name, port, protocol
376 - `cluster_ip` Internal ip to be used inside k8s cluster
377 - `external_ip` List of external ips (in case they are available)
378 """
379
380 @abc.abstractmethod
381 async def get_service(
382 self, cluster_uuid: str, service_name: str, namespace: str = None
383 ) -> object:
384 """
385 Obtains the data of the specified service in the k8cluster.
386
387 :param cluster_uuid: UUID of a K8s cluster known by OSM
388 :param service_name: name of the K8s service in the specified namespace
389 :param namespace: K8s namespace used by the KDU instance
390 :return: If successful, it will return a list of services, Each service can have
391 the following data:
392 - `name` of the service
393 - `type` type of service in the k8 cluster
394 - `ports` List of ports offered by the service, for each port includes at least
395 name, port, protocol
396 - `cluster_ip` Internal ip to be used inside k8s cluster
397 - `external_ip` List of external ips (in case they are available)
398 """
399
400 """
401 ####################################################################################
402 ################################### P R I V A T E ##################################
403 ####################################################################################
404 """
405
406 async def write_app_status_to_db(
407 self, db_dict: dict, status: str, detailed_status: str, operation: str
408 ) -> bool:
409
410 if not self.db:
411 self.warning("No db => No database write")
412 return False
413
414 if not db_dict:
415 self.warning("No db_dict => No database write")
416 return False
417
418 self.log.debug("status={}".format(status))
419
420 try:
421
422 the_table = db_dict["collection"]
423 the_filter = db_dict["filter"]
424 the_path = db_dict["path"]
425 if not the_path[-1] == ".":
426 the_path = the_path + "."
427 update_dict = {
428 the_path + "operation": operation,
429 the_path + "status": status,
430 the_path + "detailed-status": detailed_status,
431 the_path + "status-time": str(time.time()),
432 }
433
434 self.db.set_one(
435 table=the_table,
436 q_filter=the_filter,
437 update_dict=update_dict,
438 fail_on_empty=True,
439 )
440
441 # database callback
442 if self.on_update_db:
443 if asyncio.iscoroutinefunction(self.on_update_db):
444 await self.on_update_db(
445 the_table, the_filter, the_path, update_dict
446 )
447 else:
448 self.on_update_db(the_table, the_filter, the_path, update_dict)
449
450 return True
451
452 except Exception as e:
453 self.log.info("Exception writing status to database: {}".format(e))
454 return False