Add juju support to lcm
[osm/RO.git] / lcm / osm_lcm / vca.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 from juju_api import JujuApi
5 from juju.model import ModelObserver
6 import logging
7 import os
8 import os.path
9 import re
10
11
12 class VCAMonitor(ModelObserver):
13 """Monitor state changes within the Juju Model."""
14 context = None
15
16 async def on_change(self, delta, old, new, model):
17 """React to changes in the Juju model."""
18 status = None
19 db_nsr = self.context['db_nsr']
20 vnf_id = self.context['vnf_id']
21
22 nsr_lcm = db_nsr["_admin"]["deploy"]
23 nsr_id = nsr_lcm["id"]
24 application = self.context['application']
25
26 if delta.entity == "unit":
27 # We only care about changes to a unit
28 if delta.type == "add" and old is None:
29 if new and new.application == application:
30 status = "BUILD"
31 elif delta.type == "change":
32 if new and new.application == application:
33 if new.agent_status == "idle":
34 if new.workload_status in ("active", "blocked"):
35 status = "ACTIVE"
36
37 elif delta.type == "remove" and new is None:
38 if new and new.application == application:
39 status = "DELETING"
40
41 if status:
42 nsr_lcm["VCA"][vnf_id]['operational-status'] = status
43
44 # TODO: Clean this up, and make it work with deletes (if we need
45 # TODO: to update the database post-delete)
46 # Figure out if we're finished configuring
47 count = len(nsr_lcm["VCA"])
48 active = 0
49 for vnf_id in nsr_lcm["VCA"]:
50 if nsr_lcm["VCA"][vnf_id]['operational-status'] == "ACTIVE":
51 active += 1
52 if active == count:
53 db_nsr["config-status"] = "done"
54 else:
55 db_nsr["config-status"] = "configuring {}/{}".format(active, count)
56
57 try:
58 self.context['db'].replace(
59 "nsrs",
60 nsr_id,
61 db_nsr
62 )
63
64 # self.context['db'].replace(
65 # "nsr_lcm",
66 # {"id": self.context['nsr_lcm']['id']},
67 # self.context['nsr_lcm']
68 # )
69 except Exception as e:
70 # I've seen this happen when we handle a delete, because the
71 # db record is gone by the time we've finished deleting
72 # the charms.
73 print("Error updating database: ", e)
74
75 pass
76
77
78 def GetJujuApi(config):
79 # Quiet logging from the websocket library. If you want to see
80 # everything sent over the wire, set this to DEBUG.
81 logging.basicConfig(level=logging.DEBUG)
82
83 ws_logger = logging.getLogger('websockets.protocol')
84 ws_logger.setLevel(logging.INFO)
85
86 api = JujuApi(server=config['host'],
87 port=config['port'],
88 user=config['user'],
89 secret=config['secret'],
90 log=ws_logger,
91 model_name='default'
92 )
93 return api
94
95
96 def get_vnf_unique_name(nsr_name, vnfr_name, member_vnf_index):
97 """Get the unique VNF name.
98 Charm names accepts only a to z and non-consecutive - characters."""
99 name = "{}-{}-{}".format(nsr_name, vnfr_name, member_vnf_index)
100 new_name = ''
101 for c in name:
102 if c.isdigit():
103 c = chr(97 + int(c))
104 elif not c.isalpha():
105 c = "-"
106 new_name += c
107 return re.sub('\-+', '-', new_name.lower())
108
109
110 def get_initial_config(initial_config_primitive, mgmt_ip):
111 config = {}
112 for primitive in initial_config_primitive:
113 if primitive['name'] == 'config':
114 for parameter in primitive['parameter']:
115 param = parameter['name']
116 if parameter['value'] == "<rw_mgmt_ip>":
117 config[param] = mgmt_ip
118 else:
119 config[param] = parameter['value']
120 return config
121
122
123 async def DeployApplication(vcaconfig, db, db_nsr, vnfd,
124 vnfd_index, charm_path):
125 """
126 Deploy a charm.
127
128 Deploy a VNF configuration charm from a local directory.
129 :param dict vcaconfig: The VCA portion of the LCM Configuration
130 :param object vnfd: The VNF descriptor
131 ...
132 :param int vnfd_index: The index of the vnf.
133
134 :Example:
135
136 DeployApplication(...)
137 """
138 nsr_lcm = db_nsr["_admin"]["deploy"]
139 nsr_id = nsr_lcm["id"]
140 vnf_id = vnfd['id']
141
142 if "proxy" in vnfd["vnf-configuration"]["juju"]:
143 use_proxy = vnfd["vnf-configuration"]["juju"]["proxy"]
144 else:
145 # TBD: We need this to handle a full charm
146 use_proxy = True
147
148 application = get_vnf_unique_name(
149 db_nsr["name"].lower().strip(),
150 vnfd['id'],
151 vnfd_index,
152 )
153
154 api = GetJujuApi(vcaconfig)
155
156 await api.login()
157 if api.authenticated:
158 charm = os.path.basename(charm_path)
159
160 # Set the INIT state; further operational status updates
161 # will be made by the VCAMonitor
162 nsr_lcm["VCA"][vnf_id] = {}
163 nsr_lcm["VCA"][vnf_id]['operational-status'] = 'INIT'
164 nsr_lcm["VCA"][vnf_id]['application'] = application
165
166 db.replace("nsrs", nsr_id, db_nsr)
167
168 model = await api.get_model()
169 context = {
170 'application': application,
171 'vnf_id': vnf_id,
172 'db_nsr': db_nsr,
173 'db': db,
174 }
175 mon = VCAMonitor()
176 mon.context = context
177 model.add_observer(mon)
178
179 await api.deploy_application(charm,
180 name=application,
181 path=charm_path,
182 )
183
184 # Get and apply the initial config primitive
185 cfg = get_initial_config(
186 vnfd["vnf-configuration"].get(
187 "initial-config-primitive"
188 ),
189 nsr_lcm['nsr_ip'][vnfd_index]
190 )
191
192 await api.apply_config(cfg, application)
193
194 await api.logout()
195
196
197 async def RemoveApplication(vcaconfig, db, db_nsr, vnfd, vnfd_index):
198 """
199 Remove an application from the Juju Controller
200
201 Removed the named application and it's charm from the Juju controller.
202
203 :param object loop: The event loop.
204 :param str application_name: The unique name of the application.
205
206 :Example:
207
208 RemoveApplication(loop, "ping_vnf")
209 RemoveApplication(loop, "pong_vnf")
210 """
211 nsr_lcm = db_nsr["_admin"]["deploy"]
212 vnf_id = vnfd['id']
213 application = nsr_lcm["VCA"][vnf_id]['application']
214
215 api = GetJujuApi(vcaconfig)
216
217 await api.login()
218 if api.authenticated:
219 model = await api.get_model()
220 context = {
221 'application': application,
222 'vnf_id': vnf_id,
223 'db_nsr': db_nsr,
224 'db': db,
225 }
226
227 mon = VCAMonitor()
228 mon.context = context
229 model.add_observer(mon)
230
231 print("VCA: Removing application {}".format(application))
232 await api.remove_application(application)
233 await api.logout()