158 lines
6.7 KiB
Python
158 lines
6.7 KiB
Python
'''ODL Controller API integration'''
|
|
import requests
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from charmhelpers.core.hookenv import log
|
|
from charmhelpers.core.decorators import retry_on_exception
|
|
|
|
|
|
class ODLInteractionFatalError(Exception):
|
|
''' Generic exception for failures in interaction with ODL '''
|
|
pass
|
|
|
|
|
|
class ODLConfig(requests.Session):
|
|
|
|
def __init__(self, username, password, host, port='8181'):
|
|
super(ODLConfig, self).__init__()
|
|
self.mount("http://", requests.adapters.HTTPAdapter(max_retries=5))
|
|
self.base_url = 'http://{}:{}'.format(host, port)
|
|
self.auth = (username, password)
|
|
self.proxies = {}
|
|
self.timeout = 10
|
|
self.conf_url = self.base_url + '/restconf/config'
|
|
self.oper_url = self.base_url + '/restconf/operational'
|
|
self.netmap_url = self.conf_url + '/neutron-device-map:neutron_net_map'
|
|
self.node_query_url = self.oper_url + '/opendaylight-inventory:nodes/'
|
|
yang_mod_path = ('/opendaylight-inventory:nodes/node/'
|
|
'controller-config/yang-ext:mount/config:modules')
|
|
self.node_mount_url = self.conf_url + yang_mod_path
|
|
|
|
@retry_on_exception(5, base_delay=30,
|
|
exc_type=requests.exceptions.ConnectionError)
|
|
def contact_odl(self, request_type, url, headers=None, data=None,
|
|
whitelist_rcs=None, retry_rcs=None):
|
|
response = self.request(request_type, url, data=data, headers=headers)
|
|
ok_codes = [requests.codes.ok, requests.codes.no_content]
|
|
retry_codes = [requests.codes.service_unavailable]
|
|
if whitelist_rcs:
|
|
ok_codes.extend(whitelist_rcs)
|
|
if retry_rcs:
|
|
retry_codes.extend(retry_rcs)
|
|
if response.status_code not in ok_codes:
|
|
if response.status_code in retry_codes:
|
|
msg = "Recieved {} from ODL on {}".format(response.status_code,
|
|
url)
|
|
raise requests.exceptions.ConnectionError(msg)
|
|
else:
|
|
msg = "Contact failed status_code={}, {}".format(
|
|
response.status_code, url)
|
|
raise ODLInteractionFatalError(msg)
|
|
return response
|
|
|
|
def get_networks(self):
|
|
log('Querying macs registered with odl')
|
|
# No netmap may have been registered yet, so 404 is ok
|
|
odl_req = self.contact_odl(
|
|
'GET', self.netmap_url, whitelist_rcs=[requests.codes.not_found])
|
|
if not odl_req:
|
|
log('neutron_net_map not found in ODL')
|
|
return {}
|
|
odl_json = odl_req.json()
|
|
if odl_json.get('neutron_net_map'):
|
|
log('neutron_net_map returned by ODL')
|
|
return odl_json['neutron_net_map']
|
|
else:
|
|
log('neutron_net_map NOT returned by ODL')
|
|
return {}
|
|
|
|
def delete_net_device_entry(self, net, device_name):
|
|
obj_url = self.netmap_url + \
|
|
'physicalNetwork/{}/device/{}'.format(net, device_name)
|
|
self.contact_odl('DELETE', obj_url)
|
|
|
|
def get_odl_registered_nodes(self):
|
|
log('Querying nodes registered with odl')
|
|
odl_req = self.contact_odl('GET', self.node_query_url)
|
|
odl_json = odl_req.json()
|
|
odl_node_ids = []
|
|
if odl_json.get('nodes'):
|
|
odl_nodes = odl_json['nodes'].get('node', [])
|
|
odl_node_ids = [entry['id'] for entry in odl_nodes]
|
|
log('Following nodes are registered: ' + ' '.join(odl_node_ids))
|
|
return odl_node_ids
|
|
|
|
def odl_register_node(self, device_name, ip):
|
|
log('Registering node {} ({}) with ODL'.format(device_name, ip))
|
|
payload = self.render_node_xml(device_name, ip)
|
|
headers = {'Content-Type': 'application/xml'}
|
|
# Strictly a client should not retry on recipt of a bad_request (400)
|
|
# but ODL return 400s while it is initialising
|
|
self.contact_odl(
|
|
'POST', self.node_mount_url, headers=headers, data=payload,
|
|
retry_rcs=[requests.codes.bad_request])
|
|
|
|
def odl_register_macs(self, device_name, network, interface, mac,
|
|
device_type='vhostuser'):
|
|
log('Registering {} and {} on {}'.format(network, interface, mac))
|
|
payload = self.render_mac_xml(device_name, network, interface, mac,
|
|
device_type)
|
|
headers = {'Content-Type': 'application/json'}
|
|
self.contact_odl(
|
|
'POST', self.netmap_url, headers=headers, data=payload)
|
|
|
|
def get_macs_networks(self, mac):
|
|
registered_networks = self.get_networks()
|
|
nets = []
|
|
phy_nets = registered_networks.get('physicalNetwork')
|
|
if phy_nets:
|
|
for network in phy_nets:
|
|
for device in network.get('device', []):
|
|
for interface in device['interface']:
|
|
if interface['macAddress'] == mac:
|
|
nets.append(network['name'])
|
|
return nets
|
|
|
|
def is_device_registered(self, device_name):
|
|
return device_name in self.get_odl_registered_nodes()
|
|
|
|
def is_net_device_registered(self, net_name, device_name, interface_name,
|
|
mac, device_type='vhostuser'):
|
|
networks = self.get_networks()
|
|
phy_nets = networks.get('physicalNetwork')
|
|
if phy_nets:
|
|
for net in phy_nets:
|
|
if net_name == net['name']:
|
|
for dev in net.get('device', []):
|
|
if device_name == dev['device-name'] \
|
|
and dev['device-type'] == device_type:
|
|
for interface in dev['interface']:
|
|
if (interface_name ==
|
|
interface['interface-name'] and
|
|
mac == interface['macAddress']):
|
|
return True
|
|
return False
|
|
|
|
def render_node_xml(self, device_name, ip, user='admin', password='admin'):
|
|
env = Environment(loader=FileSystemLoader('templates'))
|
|
template = env.get_template('odl_registration')
|
|
node_xml = template.render(
|
|
vpp_host=device_name,
|
|
vpp_ip=ip,
|
|
vpp_username=user,
|
|
vpp_password=password,
|
|
)
|
|
return node_xml
|
|
|
|
def render_mac_xml(self, device_name, network, interface, mac,
|
|
device_type='vhostuser'):
|
|
env = Environment(loader=FileSystemLoader('templates'))
|
|
template = env.get_template('mac_registration')
|
|
mac_xml = template.render(
|
|
vpp_host=device_name,
|
|
network=network,
|
|
interface=interface,
|
|
mac=mac,
|
|
device_type=device_type,
|
|
)
|
|
return mac_xml
|