Enabling Flake8 and import sorting
[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 json
18 import logging
19
20 from osm_ro_plugin.sdnconn import SdnConnectorError
21 from osm_rosdn_juniper_contrail.rest_lib import ContrailHttp
22 from osm_rosdn_juniper_contrail.rest_lib import NotFound
23
24 # from osm_rosdn_juniper_contrail.rest_lib import DuplicateFound
25 # from osm_rosdn_juniper_contrail.rest_lib import HttpException
26
27
28 class UnderlayApi:
29 """Class with CRUD operations for the underlay API"""
30
31 def __init__(self, url, config=None, user=None, password=None, logger=None):
32 self.logger = logger or logging.getLogger("ro.sdn.junipercontrail.sdnapi")
33 self.controller_url = url
34
35 if not url:
36 raise SdnConnectorError("'url' must be provided")
37
38 if not url.startswith("http"):
39 url = "http://" + url
40
41 if not url.endswith("/"):
42 url = url + "/"
43
44 self.url = url
45 self.auth_url = None
46 self.project = None
47 self.domain = None
48 self.asn = None
49 self.fabric = None
50
51 if config:
52 self.auth_url = config.get("auth_url")
53 self.project = config.get("project")
54 self.domain = config.get("domain")
55 self.asn = config.get("asn")
56 self.fabric = config.get("fabric")
57
58 # Init http headers for all requests
59 self.http_header = {"Content-Type": "application/json"}
60
61 if user:
62 self.user = user
63
64 if password:
65 self.password = password
66
67 self.logger.debug(
68 "Config parameters for the underlay controller: auth_url: {}, project: {},"
69 " domain: {}, user: {}, password: {}".format(
70 self.auth_url, self.project, self.domain, self.user, self.password
71 )
72 )
73
74 auth_dict = {}
75 auth_dict["auth"] = {}
76 auth_dict["auth"]["scope"] = {}
77 auth_dict["auth"]["scope"]["project"] = {}
78 auth_dict["auth"]["scope"]["project"]["domain"] = {}
79 auth_dict["auth"]["scope"]["project"]["domain"]["id"] = self.domain
80 auth_dict["auth"]["scope"]["project"]["name"] = self.project
81 auth_dict["auth"]["identity"] = {}
82 auth_dict["auth"]["identity"]["methods"] = ["password"]
83 auth_dict["auth"]["identity"]["password"] = {}
84 auth_dict["auth"]["identity"]["password"]["user"] = {}
85 auth_dict["auth"]["identity"]["password"]["user"]["name"] = self.user
86 auth_dict["auth"]["identity"]["password"]["user"]["password"] = self.password
87 auth_dict["auth"]["identity"]["password"]["user"]["domain"] = {}
88 auth_dict["auth"]["identity"]["password"]["user"]["domain"]["id"] = self.domain
89 self.auth_dict = auth_dict
90
91 # Init http lib
92 auth_info = {"auth_url": self.auth_url, "auth_dict": auth_dict}
93 self.http = ContrailHttp(auth_info, self.logger)
94
95 def check_auth(self):
96 response = self.http.get_cmd(url=self.auth_url, headers=self.http_header)
97
98 return response
99
100 # Helper methods for CRUD operations
101 def get_all_by_type(self, controller_url, type):
102 endpoint = controller_url + type
103 response = self.http.get_cmd(url=endpoint, headers=self.http_header)
104
105 return response.get(type)
106
107 def get_by_uuid(self, type, uuid):
108 try:
109 endpoint = self.controller_url + type + "/{}".format(uuid)
110 response = self.http.get_cmd(url=endpoint, headers=self.http_header)
111
112 return response.get(type)
113 except NotFound:
114 return None
115
116 def delete_by_uuid(self, controller_url, type, uuid):
117 endpoint = controller_url + type + "/{}".format(uuid)
118 self.http.delete_cmd(url=endpoint, headers=self.http_header)
119
120 def get_uuid_from_fqname(self, type, fq_name):
121 """
122 Obtain uuid from fqname
123 Returns: If resource not found returns None
124 In case of error raises an Exception
125 """
126 payload = {"type": type, "fq_name": fq_name}
127
128 try:
129 endpoint = self.controller_url + "fqname-to-id"
130 resp = self.http.post_cmd(
131 url=endpoint, headers=self.http_header, post_fields_dict=payload
132 )
133
134 return json.loads(resp).get("uuid")
135 except NotFound:
136 return None
137
138 def get_by_fq_name(self, type, fq_name):
139 # Obtain uuid by fqdn and then get data by uuid
140 uuid = self.get_uuid_from_fqname(type, fq_name)
141
142 if uuid:
143 return self.get_by_uuid(type, uuid)
144 else:
145 return None
146
147 def delete_ref(self, type, uuid, ref_type, ref_uuid, ref_fq_name):
148 payload = {
149 "type": type,
150 "uuid": uuid,
151 "ref-type": ref_type,
152 "ref-fq-name": ref_fq_name,
153 "operation": "DELETE",
154 }
155 endpoint = self.controller_url + "ref-update"
156 resp = self.http.post_cmd(
157 url=endpoint, headers=self.http_header, post_fields_dict=payload
158 )
159
160 return resp
161
162 # Aux methods to avoid code duplication of name conventions
163 def get_vpg_name(self, switch_id, switch_port):
164 return "{}_{}".format(switch_id, switch_port).replace(":", "_")
165
166 def get_vmi_name(self, switch_id, switch_port, vlan):
167 return "{}_{}-{}".format(switch_id, switch_port, vlan).replace(":", "_")
168
169 # Virtual network operations
170
171 def create_virtual_network(self, name, vni):
172 self.logger.debug("create vname, name: {}, vni: {}".format(name, vni))
173 routetarget = "{}:{}".format(self.asn, vni)
174 vnet_dict = {
175 "virtual-network": {
176 "virtual_network_properties": {
177 "vxlan_network_identifier": vni,
178 },
179 "parent_type": "project",
180 "fq_name": [self.domain, self.project, name],
181 "route_target_list": {"route_target": ["target:" + routetarget]},
182 }
183 }
184 endpoint = self.controller_url + "virtual-networks"
185 resp = self.http.post_cmd(
186 url=endpoint, headers=self.http_header, post_fields_dict=vnet_dict
187 )
188
189 if not resp:
190 raise SdnConnectorError("Error creating virtual network: empty response")
191
192 vnet_info = json.loads(resp)
193 self.logger.debug("created vnet, vnet_info: {}".format(vnet_info))
194
195 return vnet_info.get("virtual-network").get("uuid"), vnet_info.get(
196 "virtual-network"
197 )
198
199 def get_virtual_networks(self):
200 return self.get_all_by_type("virtual-networks")
201
202 def get_virtual_network(self, network_id):
203 return self.get_by_uuid("virtual-network", network_id)
204
205 def delete_virtual_network(self, network_id):
206 self.logger.debug("delete vnet uuid: {}".format(network_id))
207 self.delete_by_uuid(self.controller_url, "virtual-network", network_id)
208 self.logger.debug("deleted vnet uuid: {}".format(network_id))
209
210 # Vpg operations
211
212 def create_vpg(self, switch_id, switch_port):
213 self.logger.debug(
214 "create vpg, switch_id: {}, switch_port: {}".format(switch_id, switch_port)
215 )
216 vpg_name = self.get_vpg_name(switch_id, switch_port)
217 vpg_dict = {
218 "virtual-port-group": {
219 "parent_type": "fabric",
220 "fq_name": ["default-global-system-config", self.fabric, vpg_name],
221 }
222 }
223 endpoint = self.controller_url + "virtual-port-groups"
224 resp = self.http.post_cmd(
225 url=endpoint, headers=self.http_header, post_fields_dict=vpg_dict
226 )
227
228 if not resp:
229 raise SdnConnectorError("Error creating virtual port group: empty response")
230
231 vpg_info = json.loads(resp)
232 self.logger.debug("created vpg, vpg_info: {}".format(vpg_info))
233
234 return vpg_info.get("virtual-port-group").get("uuid"), vpg_info.get(
235 "virtual-port-group"
236 )
237
238 def get_vpgs(self):
239 return self.get_all_by_type(self.controller_url, "virtual-port-groups")
240
241 def get_vpg(self, vpg_id):
242 return self.get_by_uuid(self.controller_url, "virtual-port-group", vpg_id)
243
244 def get_vpg_by_name(self, vpg_name):
245 fq_name = ["default-global-system-config", self.fabric, vpg_name]
246
247 return self.get_by_fq_name("virtual-port-group", fq_name)
248
249 def delete_vpg(self, vpg_id):
250 self.logger.debug("delete vpg, uuid: {}".format(vpg_id))
251 self.delete_by_uuid(self.controller_url, "virtual-port-group", vpg_id)
252 self.logger.debug("deleted vpg, uuid: {}".format(vpg_id))
253
254 def create_vmi(self, switch_id, switch_port, network, vlan):
255 self.logger.debug(
256 "create vmi, switch_id: {}, switch_port: {}, network: {}, vlan: {}".format(
257 switch_id, switch_port, network, vlan
258 )
259 )
260 vmi_name = self.get_vmi_name(switch_id, switch_port, vlan)
261 vpg_name = self.get_vpg_name(switch_id, switch_port)
262 profile_dict = {
263 "local_link_information": [
264 {
265 "port_id": switch_port.replace(":", "_"),
266 "switch_id": switch_port.replace(":", "_"),
267 "switch_info": switch_id,
268 "fabric": self.fabric,
269 }
270 ]
271 }
272 vmi_dict = {
273 "virtual-machine-interface": {
274 "parent_type": "project",
275 "fq_name": [self.domain, self.project, vmi_name],
276 "virtual_network_refs": [{"to": [self.domain, self.project, network]}],
277 "virtual_machine_interface_properties": {
278 "sub_interface_vlan_tag": vlan
279 },
280 "virtual_machine_interface_bindings": {
281 "key_value_pair": [
282 {"key": "vnic_type", "value": "baremetal"},
283 {"key": "vif_type", "value": "vrouter"},
284 {"key": "vpg", "value": vpg_name},
285 {"key": "profile", "value": json.dumps(profile_dict)},
286 ]
287 },
288 }
289 }
290 endpoint = self.controller_url + "virtual-machine-interfaces"
291 self.logger.debug("vmi_dict: {}".format(vmi_dict))
292 resp = self.http.post_cmd(
293 url=endpoint,
294 headers=self.http_header,
295 post_fields_dict=vmi_dict,
296 )
297
298 if not resp:
299 raise SdnConnectorError("Error creating vmi: empty response")
300
301 vmi_info = json.loads(resp)
302 self.logger.debug("created vmi, info: {}".format(vmi_info))
303
304 return vmi_info.get("virtual-machine-interface").get("uuid"), vmi_info.get(
305 "virtual-machine-interface"
306 )
307
308 def get_vmi(self, vmi_uuid):
309 return self.get_by_uuid(
310 self.controller_url, "virtual-machine-interface", vmi_uuid
311 )
312
313 def delete_vmi(self, uuid):
314 self.logger.debug("delete vmi uuid: {}".format(uuid))
315 self.delete_by_uuid(self.controller_url, "virtual-machine-interface", uuid)
316 self.logger.debug("deleted vmi: {}".format(uuid))
317
318 def unref_vmi_vpg(self, vpg_id, vmi_id, vmi_fq_name):
319 self.delete_ref(
320 "virtual-port-group",
321 vpg_id,
322 "virtual-machine-interface",
323 vmi_id,
324 vmi_fq_name,
325 )