# # Copyright (c) 2015-2016 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import json import six import uuid from nfv_common import debug from nfv_common.helpers import Constant from nfv_common.helpers import Constants from nfv_common.helpers import Singleton from nfv_plugins.nfvi_plugins.openstack.objects import OPENSTACK_SERVICE from nfv_plugins.nfvi_plugins.openstack.rest_api import rest_api_request DLOG = debug.debug_get_logger('nfv_plugins.nfvi_plugins.openstack.neutron') @six.add_metaclass(Singleton) class NeutronExtensionNames(Constants): """ Neutron Extension Name Constants """ HOST = Constant('host') AGENT = Constant('agent') @six.add_metaclass(Singleton) class NetworkAdministrativeState(Constants): """ NETWORK ADMINISTRATIVE STATE Constants """ UP = Constant(True) DOWN = Constant(False) @six.add_metaclass(Singleton) class NetworkStatus(Constants): """ NETWORK STATUS Constants """ ACTIVE = Constant('ACTIVE') BUILD = Constant('BUILD') DOWN = Constant('DOWN') ERROR = Constant('ERROR') @six.add_metaclass(Singleton) class AgentType(Constants): """ AGENT TYPE Constants """ L3 = Constant('L3 agent') DHCP = Constant('DHCP agent') # Constant Instantiation EXTENSION_NAMES = NeutronExtensionNames() NETWORK_ADMIN_STATE = NetworkAdministrativeState() NETWORK_STATUS = NetworkStatus() AGENT_TYPE = AgentType() def get_network_agents(token, host_name): """ Retrieve all network agents for a host. """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/agents?host=" + host_name api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" response = rest_api_request(token, "GET", api_cmd, api_cmd_headers) result_data = response.result_data['agents'] return url, api_cmd, api_cmd_headers, result_data def lookup_extension(extension_name, extensions): """ Lookup an extension for OpenStack Neutron """ if extensions is None: return None extension_list = extensions.get('extensions', None) if extension_list is None: return None for extension in extension_list: alias = extension.get('alias', None) if alias is not None: if alias == extension_name: return extension return None def get_extensions(token): """ Asks OpenStack Neutron for a list of extensions """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: return dict() api_cmd = url + "/v2.0/extensions.json" api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" response = rest_api_request(token, "GET", api_cmd) return response def get_networks(token, page_limit=None, next_page=None): """ Asks OpenStack Neutron for a list of networks """ if next_page is None: url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/networks" if page_limit is not None: api_cmd += "?limit=%s" % page_limit else: api_cmd = next_page api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "GET", api_cmd, api_cmd_headers) return response def create_network(token, network_name, network_type, segmentation_id, physical_network, shared): """ Asks OpenStack Neutron to create a network """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/networks" api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" network = dict() network['name'] = network_name network['shared'] = shared if 'flat' == network_type: network['provider:network_type'] = network_type network['provider:physical_network'] = physical_network else: network['provider:network_type'] = network_type network['provider:segmentation_id'] = segmentation_id network['provider:physical_network'] = physical_network api_cmd_payload = dict() api_cmd_payload['network'] = network response = rest_api_request(token, "POST", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def update_network(token, network_id, admin_state=None, shared=None): """ Asks OpenStack Neutron to update a network """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/networks/%s" % network_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" network = dict() if admin_state is not None: if NETWORK_ADMIN_STATE.UP == admin_state: network['admin_state_up'] = True else: network['admin_state_up'] = False if shared is not None: network['shared'] = shared api_cmd_payload = dict() api_cmd_payload['network'] = network response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def delete_network(token, network_id): """ Asks OpenStack Neutron to delete a network """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/networks/%s" % network_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "DELETE", api_cmd, api_cmd_headers) return response def get_network(token, network_id): """ Asks OpenStack Neutron for network details """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/networks/%s" % network_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "GET", api_cmd, api_cmd_headers) return response def get_subnets(token, page_limit=None, next_page=None): """ Ask OpenStack Neutron for a list of subnets """ if next_page is None: url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/subnets" if page_limit is not None: api_cmd += "?limit=%s" % page_limit else: api_cmd = next_page api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "GET", api_cmd, api_cmd_headers) return response def create_subnet(token, network_id, subnet_name, ip_version, cidr, gateway_ip, dhcp_enabled): """ Ask OpenStack Neutron to create a subnet """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/subnets" api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" subnet = dict() if subnet_name is not None: subnet['name'] = subnet_name subnet['network_id'] = network_id subnet['ip_version'] = ip_version subnet['cidr'] = cidr subnet['enable_dhcp'] = dhcp_enabled if gateway_ip is not None: subnet['gateway_ip'] = gateway_ip api_cmd_payload = dict() api_cmd_payload['subnet'] = subnet response = rest_api_request(token, "POST", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def update_subnet(token, subnet_id, gateway_ip=None, delete_gateway=False, dhcp_enabled=None): """ Ask OpenStack Neutron to update a subnet """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/subnets/%s" % subnet_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" subnet = dict() if gateway_ip is not None: subnet['gateway_ip'] = gateway_ip elif delete_gateway: subnet['gateway_ip'] = None if dhcp_enabled is not None: subnet['enable_dhcp'] = dhcp_enabled api_cmd_payload = dict() api_cmd_payload['subnet'] = subnet response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def delete_subnet(token, subnet_id): """ Asks OpenStack Neutron to delete a subnet """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/subnets/%s" % subnet_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "DELETE", api_cmd, api_cmd_headers) return response def get_subnet(token, subnet_id): """ Asks OpenStack Neutron for subnet details """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/subnets/%s" % subnet_id api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "GET", api_cmd, api_cmd_headers) return response def create_host_services(token, hostname, host_uuid): """ Asks OpenStack Neutron to create a host """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts.json" api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" payload = dict() payload['availability'] = 'down' payload['id'] = host_uuid payload['name'] = hostname api_cmd_payload = dict() api_cmd_payload['host'] = payload response = rest_api_request(token, "POST", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def delete_host_services(token, host_uuid): """ Asks OpenStack Neutron to delete a host """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts/%s" % host_uuid api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' response = rest_api_request(token, "DELETE", api_cmd, api_cmd_headers) return response def delete_network_agents(token, host_name): """ Asks OpenStack Neutron to delete agents for a host """ try: url, api_cmd, api_cmd_headers, result_data = get_network_agents( token, host_name) num_agents_found = 0 supported_agents = [AGENT_TYPE.L3, AGENT_TYPE.DHCP] for agent in result_data: if (agent['host'] == host_name and agent['agent_type'] in supported_agents): api_cmd = url + "/v2.0/agents/%s" % agent['id'] response = rest_api_request(token, "DELETE", api_cmd, api_cmd_headers) num_agents_found = num_agents_found + 1 except Exception as e: DLOG.exception("Caught exception trying to delete host %s agent: %s" % (host_name, e)) return False if num_agents_found != len(supported_agents): DLOG.warn("Host %s, did not find expected agents to delete" % host_name) return True def delete_host_services_by_name(token, host_name, host_uuid, only_if_changed=False): """ Asks OpenStack Neutron to delete a host by name """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts.json?fields=id&fields=name" api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" response = rest_api_request(token, "GET", api_cmd) for host in response.result_data['hosts']: if host['name'] == host_name: if only_if_changed: if host['id'] != host_uuid: api_cmd = url + "/v2.0/hosts/%s" % host['id'] rest_api_request(token, "DELETE", api_cmd) else: api_cmd = url + "/v2.0/hosts/%s" % host['id'] rest_api_request(token, "DELETE", api_cmd) def enable_host_services(token, host_uuid): """ Asks OpenStack Neutron to enable a host """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts/%s" % host_uuid api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" payload = dict() payload['availability'] = 'up' api_cmd_payload = dict() api_cmd_payload['host'] = payload response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def enable_network_agents(token, host_name): """ Asks OpenStack Neutron to enable agents on a host. Set admin_state_up to True for all agents found for a host, but ensure that the set of supported agents that are managed are all alive and admin_state_up is successfully set to True before declaring success. """ try: url, api_cmd, api_cmd_headers, result_data = get_network_agents( token, host_name) payload = dict() payload['admin_state_up'] = True api_cmd_payload = dict() api_cmd_payload['agent'] = payload num_agents_found = 0 all_enabled = True supported_agents = [AGENT_TYPE.L3, AGENT_TYPE.DHCP] for agent in result_data: if agent['host'] == host_name: alive = agent.get('alive', False) supported = agent['agent_type'] in supported_agents if (not alive) and supported: DLOG.error("Host %s %s not alive" % (host_name, agent['agent_type'])) return False admin_up = agent['admin_state_up'] if not admin_up: api_cmd = url + "/v2.0/agents/%s" % agent['id'] response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) admin_up = response.result_data['agent']['admin_state_up'] if supported: all_enabled = all_enabled and admin_up num_agents_found = num_agents_found + 1 except Exception as e: DLOG.exception("Caught exception trying to enable host %s agents: %s" % (host_name, e)) return False if num_agents_found != len(supported_agents): DLOG.error("Host %s, did not find expected agents to enable" % host_name) return False return all_enabled def disable_host_services(token, host_uuid): """ Asks OpenStack Neutron to disable a host """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts/%s" % host_uuid api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" payload = dict() payload['availability'] = 'down' api_cmd_payload = dict() api_cmd_payload['host'] = payload response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) return response def disable_network_agents(token, host_name): """ Asks OpenStack Neutron to disable the set of supported agents that are managed on a host by setting the admin_state_up parameter to False. Other agents are left alone. """ try: url, api_cmd, api_cmd_headers, result_data = get_network_agents( token, host_name) payload = dict() payload['admin_state_up'] = False api_cmd_payload = dict() api_cmd_payload['agent'] = payload num_agents_found = 0 all_disabled = True supported_agents = [AGENT_TYPE.L3, AGENT_TYPE.DHCP] for agent in result_data: if (agent['host'] == host_name and agent['agent_type'] in supported_agents): api_cmd = url + "/v2.0/agents/%s" % agent['id'] response = rest_api_request(token, "PUT", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)) all_disabled = all_disabled and not \ response.result_data['agent']['admin_state_up'] num_agents_found = num_agents_found + 1 except Exception as e: DLOG.exception("Caught exception trying to disable host %s agents: %s" % (host_name, e)) return False if num_agents_found != len(supported_agents): DLOG.error("Host %s, did not find expected agents to disable" % host_name) return False return all_disabled def query_host_services(token, host_name): """ Asks OpenStack Neutron for the state of services on a host """ url = token.get_service_url(OPENSTACK_SERVICE.NEUTRON) if url is None: raise ValueError("OpenStack Neutron URL is invalid") api_cmd = url + "/v2.0/hosts.json?fields=id&name=%s" % host_name api_cmd_headers = dict() api_cmd_headers['wrs-header'] = 'true' api_cmd_headers['Content-Type'] = "application/json" response = rest_api_request(token, "GET", api_cmd) if 0 == len(response.result_data['hosts']): return None result_data = response.result_data['hosts'][0] host_id = result_data['id'] # Deal with getting an invalid uuid try: uuid.UUID(host_id) except (TypeError, ValueError, AttributeError): DLOG.error("Neutron host query failed for %s, defaulting return " "to down." % host_name) return 'down' api_cmd = url + "/v2.0/hosts/%s" % host_id response = rest_api_request(token, "GET", api_cmd) result_data = response.result_data['host'] host_state = result_data['availability'] # Deal with invalid return value if not (host_state == 'up' or host_state == 'down'): DLOG.error("Neutron availability state query failed for %s, defaulting " "return to down." % host_name) host_state = 'down' return host_state def query_network_agents(token, host_name, check_fully_up): """ Asks OpenStack Neutron for the state of the supported agents that are managed on a host. Input parameter check_fully_up set to True will check for both alive and admin_state_up, otherwise only alive is checked. """ url, api_cmd, api_cmd_headers, result_data = get_network_agents( token, host_name) agent_state = 'up' alive = False admin_state_up = False supported_agents = [AGENT_TYPE.L3, AGENT_TYPE.DHCP] for supported_agent in supported_agents: found = False for agent in result_data: agent_type = agent.get('agent_type', '') host = agent.get('host', '') if (agent_type == supported_agent) and (host == host_name): DLOG.verbose("found agent %s for host %s" % (supported_agent, host_name)) alive = agent.get('alive', False) admin_state_up = agent.get('admin_state_up', False) # found the agent of interest. found = True break if found: if check_fully_up: if not (alive and admin_state_up): DLOG.verbose("host %s agent %s not fully up. alive: %s," " admin_state_up: %s" % (host_name, supported_agent, alive, admin_state_up)) agent_state = 'down' break else: if not alive: DLOG.verbose("host %s agent %s not alive" % (host_name, supported_agent)) agent_state = 'down' break else: DLOG.error("host %s agent %s not present" % (host_name, supported_agent)) agent_state = 'down' break return agent_state