Developing juniper contrail plugin, implenting CRUD operations and first version...
[osm/RO.git] / RO-SDN-juniper_contrail / osm_rosdn_juniper_contrail / sdn_api.py
1 # Copyright 2020 ETSI
2 #
3 # All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16
17 import logging
18 import json
19
20 from osm_ro.wim.sdnconn import SdnConnectorError
21 from osm_rosdn_juniper_contrail.rest_lib import ContrailHttp
22 from osm_rosdn_juniper_contrail.rest_lib import NotFound
23 from osm_rosdn_juniper_contrail.rest_lib import DuplicateFound
24 from osm_rosdn_juniper_contrail.rest_lib import HttpException
25
26 class UnderlayApi:
27 """ Class with CRUD operations for the underlay API """
28
29 def __init__(self, url, config=None, user=None, password=None, logger=None):
30
31 self.logger = logger or logging.getLogger("openmano.sdnconn.junipercontrail.sdnapi")
32 self.controller_url = url
33
34 if not url:
35 raise SdnConnectorError("'url' must be provided")
36 if not url.startswith("http"):
37 url = "http://" + url
38 if not url.endswith("/"):
39 url = url + "/"
40 self.url = url
41
42 auth_url = None
43 self.project = None
44 self.domain = None
45 self.asn = None
46 self.fabric = None
47 if config:
48 self.auth_url = config.get("auth_url")
49 self.project = config.get("project")
50 self.domain = config.get("domain")
51 self.asn = config.get("asn")
52 self.fabric = config.get("fabric")
53
54 # Init http headers for all requests
55 self.http_header = {'Content-Type': 'application/json'}
56
57 if user:
58 self.user = user
59
60 if password:
61 self.password = password
62
63 auth_dict = {}
64 auth_dict['auth'] = {}
65 auth_dict['auth']['scope'] = {}
66 auth_dict['auth']['scope']['project'] = {}
67 auth_dict['auth']['scope']['project']['domain'] = {}
68 auth_dict['auth']['scope']['project']['domain']["id"] = self.domain
69 auth_dict['auth']['scope']['project']['name'] = self.project
70 auth_dict['auth']['identity'] = {}
71 auth_dict['auth']['identity']['methods'] = ['password']
72 auth_dict['auth']['identity']['password'] = {}
73 auth_dict['auth']['identity']['password']['user'] = {}
74 auth_dict['auth']['identity']['password']['user']['name'] = self.user
75 auth_dict['auth']['identity']['password']['user']['password'] = self.password
76 auth_dict['auth']['identity']['password']['user']['domain'] = {}
77 auth_dict['auth']['identity']['password']['user']['domain']['id'] = self.domain
78 self.auth_dict = auth_dict
79
80 # Init http lib
81 auth_info = {"auth_url": self.auth_url, "auth_dict": auth_dict}
82 self.http = ContrailHttp(auth_info, self.logger)
83
84 def check_auth(self):
85 response = self.http.get_cmd(url=self.auth_url, headers=self.http_header)
86 return response
87
88 # Helper methods for CRUD operations
89 def get_all_by_type(self, controller_url, type):
90 endpoint = controller_url + type
91 response = self.http.get_cmd(url=endpoint, headers=self.http_header)
92 return response.get(type)
93
94 def get_by_uuid(self, type, uuid):
95 try:
96 endpoint = self.controller_url + type + "/{}".format(uuid)
97 response = self.http.get_cmd(url=endpoint, headers=self.http_header)
98 return response.get(type)
99 except NotFound:
100 return None
101
102 def delete_by_uuid(self, controller_url, type, uuid):
103 endpoint = controller_url + type + "/{}".format(uuid)
104 self.http.delete_cmd(url=endpoint, headers=self.http_header)
105
106 def get_uuid_from_fqname(self, type, fq_name):
107 """
108 Obtain uuid from fqname
109 Returns: If resource not found returns None
110 In case of error raises an Exception
111 """
112 payload = {
113 "type": type,
114 "fq_name": fq_name
115 }
116 try:
117 endpoint = self.controller_url + "fqname-to-id"
118 resp = self.http.post_cmd(url=endpoint,
119 headers=self.http_header,
120 post_fields_dict=payload)
121 return json.loads(resp).get("uuid")
122 except NotFound:
123 return None
124
125 def get_by_fq_name(self, type, fq_name):
126 # Obtain uuid by fqdn and then get data by uuid
127 uuid = self.get_uuid_from_fqname(type, fq_name)
128 if uuid:
129 return self.get_by_uuid(type, uuid)
130 else:
131 return None
132
133
134 # Aux methods to avoid code duplication of name conventions
135 def get_vpg_name(self, switch_id, switch_port):
136 return "{}_{}".format(switch_id, switch_port)
137
138 def get_vmi_name(self, switch_id, switch_port, vlan):
139 return "{}_{}-{}".format(switch_id, switch_port, vlan)
140
141 # Virtual network operations
142
143 def create_virtual_network(self, name, vni):
144 self.logger.debug("create vname, name: {}, vni: {}".format(name, vni))
145 routetarget = '{}:{}'.format(self.asn, vni)
146 vnet_dict = {
147 "virtual-network": {
148 "virtual_network_properties": {
149 "vxlan_network_identifier": vni,
150 },
151 "parent_type": "project",
152 "fq_name": [
153 self.domain,
154 self.project,
155 name
156 ],
157 "route_target_list": {
158 "route_target": [
159 "target:" + routetarget
160 ]
161 }
162 }
163 }
164 endpoint = self.controller_url + 'virtual-networks'
165 resp = self.http.post_cmd(url=endpoint,
166 headers=self.http_header,
167 post_fields_dict=vnet_dict)
168 if not resp:
169 raise SdnConnectorError('Error creating virtual network: empty response')
170 vnet_info = json.loads(resp)
171 self.logger.debug("created vnet, vnet_info: {}".format(vnet_info))
172 return vnet_info.get("virtual-network").get('uuid'), vnet_info.get("virtual-network")
173
174 def get_virtual_networks(self):
175 return self.get_all_by_type('virtual-networks')
176
177 def get_virtual_network(self, network_id):
178 return self.get_by_uuid('virtual-network', network_id)
179
180 def delete_virtual_network(self, network_id):
181 self.logger.debug("delete vnet uuid: {}".format(network_id))
182 self.delete_by_uuid(self.controller_url, 'virtual-network', network_id)
183 self.logger.debug("deleted vnet uuid: {}".format(network_id))
184
185 # Vpg operations
186
187 def create_vpg(self, switch_id, switch_port):
188 self.logger.debug("create vpg, switch_id: {}, switch_port: {}".format(switch_id, switch_port))
189 vpg_name = self.get_vpg_name(switch_id, switch_port)
190 vpg_dict = {
191 "virtual-port-group": {
192 "parent_type": "fabric",
193 "fq_name": [
194 "default-global-system-config",
195 self.fabric,
196 vpg_name
197 ]
198 }
199 }
200 endpoint = self.controller_url + 'virtual-port-groups'
201 resp = self.http.post_cmd(url=endpoint,
202 headers=self.http_header,
203 post_fields_dict=vpg_dict)
204 if not resp:
205 raise SdnConnectorError('Error creating virtual port group: empty response')
206 vpg_info = json.loads(resp)
207 self.logger.debug("created vpg, vpg_info: {}".format(vpg_info))
208 return vpg_info.get("virtual-port-group").get('uuid'), vpg_info.get("virtual-port-group")
209
210 def get_vpgs(self):
211 return self.get_all_by_type(self.controller_url, 'virtual-port-groups')
212
213 def get_vpg(self, vpg_id):
214 return self.get_by_uuid(self.controller_url, "virtual-port-group", vpg_id)
215
216 def get_vpg_by_name(self, vpg_name):
217 fq_name = [
218 "default-global-system-config",
219 self.fabric,
220 vpg_name
221 ]
222 return self.get_by_fq_name("virtual-port-group", fq_name)
223
224 def delete_vpg(self, vpg_id):
225 self.logger.debug("delete vpg, uuid: {}".format(vpg_id))
226 self.delete_by_uuid(self.controller_url, 'virtual-port-group', vpg_id)
227 self.logger.debug("deleted vpg, uuid: {}".format(vpg_id))
228
229 def create_vmi(self, switch_id, switch_port, network, vlan):
230 self.logger.debug("create vmi, switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
231 switch_id, switch_port, network, vlan))
232 vmi_name = self.get_vmi_name(switch_id, switch_port, vlan)
233 vpg_name = self.get_vpg_name(switch_id, switch_port)
234 profile_dict = {
235 "local_link_information": [
236 {
237 "port_id": switch_port,
238 "switch_id": switch_port,
239 "switch_info": switch_id,
240 "fabric": self.fabric
241 }
242 ]
243
244 }
245 vmi_dict = {
246 "virtual-machine-interface": {
247 "parent_type": "project",
248 "fq_name": [
249 self.domain,
250 self.project,
251 vmi_name
252 ],
253 "virtual_network_refs": [
254 {
255 "to": [
256 self.domain,
257 self.project,
258 network
259 ]
260 }
261 ],
262 "virtual_machine_interface_properties": {
263 "sub_interface_vlan_tag": vlan
264 },
265 "virtual_machine_interface_bindings": {
266 "key_value_pair": [
267 {
268 "key": "vnic_type",
269 "value": "baremetal"
270 },
271 {
272 "key": "vif_type",
273 "value": "vrouter"
274 },
275 {
276 "key": "vpg",
277 "value": vpg_name
278 },
279 {
280 "key": "profile",
281 "value": json.dumps(profile_dict)
282 }
283 ]
284 }
285 }
286 }
287 endpoint = self.controller_url + 'virtual-machine-interfaces'
288 self.logger.debug("vmi_dict: {}".format(vmi_dict))
289 resp = self.http.post_cmd(url=endpoint,
290 headers=self.http_header,
291 post_fields_dict=vmi_dict)
292 if not resp:
293 raise SdnConnectorError('Error creating vmi: empty response')
294 vmi_info = json.loads(resp)
295 self.logger.debug("created vmi, info: {}".format(vmi_info))
296 return vmi_info.get("virtual-machine-interface").get('uuid'), vmi_info.get("virtual-machine-interface")
297
298 def get_vmi(self, vmi_uuid):
299 return self.get_by_uuid(self.controller_url, 'virtual-machine-interface', vmi_uuid)
300
301 def delete_vmi(self, uuid):
302 self.logger.debug("delete vmi uuid: {}".format(uuid))
303 self.delete_by_uuid(self.controller_url, 'virtual-machine-interface', uuid)
304 self.logger.debug("deleted vmi: {}".format(uuid))
305