¡@

Home 

OpenStack Study: n1kv_client.py

OpenStack Index

**** CubicPower OpenStack Study ****

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 Cisco Systems, Inc.

#

# Licensed under the Apache License, Version 2.0 (the "License"); you may

# not use this file except in compliance with the License. You may obtain

# a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

# License for the specific language governing permissions and limitations

# under the License.

#

# @author: Abhishek Raut, Cisco Systems, Inc.

# @author: Rudrajit Tapadar, Cisco Systems, Inc.

import base64

import httplib2

import netaddr

from neutron.common import exceptions as n_exc

from neutron.extensions import providernet

from neutron.openstack.common import log as logging

from neutron.plugins.cisco.common import cisco_constants as c_const

from neutron.plugins.cisco.common import cisco_credentials_v2 as c_cred

from neutron.plugins.cisco.common import cisco_exceptions as c_exc

from neutron.plugins.cisco.db import network_db_v2

from neutron.plugins.cisco.extensions import n1kv

from neutron import wsgi

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

class Client(object):

"""

Client for the Cisco Nexus1000V Neutron Plugin.

This client implements functions to communicate with

Cisco Nexus1000V VSM.

For every Neutron objects, Cisco Nexus1000V Neutron Plugin

creates a corresponding object in the controller (Cisco

Nexus1000V VSM).

CONCEPTS:

Following are few concepts used in Nexus1000V VSM:

port-profiles:

Policy profiles correspond to port profiles on Nexus1000V VSM.

Port profiles are the primary mechanism by which network policy is

**** CubicPower OpenStack Study ****

    def __init__(self, **kwargs):

        """Initialize a new client for the plugin."""

        self.format = 'json'

        self.hosts = self._get_vsm_hosts()

        self.action_prefix = 'http://%s/api/n1k' % self.hosts[0]

        self.timeout = c_const.DEFAULT_HTTP_TIMEOUT

**** CubicPower OpenStack Study ****

    def list_port_profiles(self):

        """

        Fetch all policy profiles from the VSM.

        :returns: XML string

        """

        return self._get(self.port_profiles_path)

**** CubicPower OpenStack Study ****

    def list_events(self, event_type=None, epoch=None):

        """

        Fetch all events of event_type from the VSM.

        :param event_type: type of event to be listed.

        :param epoch: timestamp after which the events occurred to be listed.

        :returns: XML string

        """

        if event_type:

            self.events_path = self.events_path + '?type=' + event_type

        return self._get(self.events_path)

**** CubicPower OpenStack Study ****

    def create_bridge_domain(self, network, overlay_subtype):

        """

        Create a bridge domain on VSM.

        :param network: network dict

        :param overlay_subtype: string representing subtype of overlay network

        """

        body = {'name': network['id'] + c_const.BRIDGE_DOMAIN_SUFFIX,

                'segmentId': network[providernet.SEGMENTATION_ID],

                'subType': overlay_subtype,

                'tenantId': network['tenant_id']}

        if overlay_subtype == c_const.NETWORK_SUBTYPE_NATIVE_VXLAN:

            body['groupIp'] = network[n1kv.MULTICAST_IP]

        return self._post(self.bridge_domains_path,

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_bridge_domain(self, name):

        """

        Delete a bridge domain on VSM.

        :param name: name of the bridge domain to be deleted

        """

        return self._delete(self.bridge_domain_path % name)

**** CubicPower OpenStack Study ****

    def create_network_segment(self, network, network_profile):

        """

        Create a network segment on the VSM.

        :param network: network dict

        :param network_profile: network profile dict

        """

        body = {'publishName': network['id'],

                'description': network['name'],

                'id': network['id'],

                'tenantId': network['tenant_id'],

                'networkSegmentPool': network_profile['id'], }

        if network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_VLAN:

            body['vlan'] = network[providernet.SEGMENTATION_ID]

        elif network[providernet.NETWORK_TYPE] == c_const.NETWORK_TYPE_OVERLAY:

            body['bridgeDomain'] = (network['id'] +

                                    c_const.BRIDGE_DOMAIN_SUFFIX)

        if network_profile['segment_type'] == c_const.NETWORK_TYPE_TRUNK:

            body['mode'] = c_const.NETWORK_TYPE_TRUNK

            body['segmentType'] = network_profile['sub_type']

            if network_profile['sub_type'] == c_const.NETWORK_TYPE_VLAN:

                body['addSegments'] = network['add_segment_list']

                body['delSegments'] = network['del_segment_list']

            else:

                body['encapProfile'] = (network['id'] +

                                        c_const.ENCAPSULATION_PROFILE_SUFFIX)

        else:

            body['mode'] = 'access'

            body['segmentType'] = network_profile['segment_type']

        return self._post(self.network_segment_path % network['id'],

                          body=body)

**** CubicPower OpenStack Study ****

    def update_network_segment(self, network_segment_id, body):

        """

        Update a network segment on the VSM.

        Network segment on VSM can be updated to associate it with an ip-pool

        or update its description and segment id.

        :param network_segment_id: UUID representing the network segment

        :param body: dict of arguments to be updated

        """

        return self._post(self.network_segment_path % network_segment_id,

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_network_segment(self, network_segment_id):

        """

        Delete a network segment on the VSM.

        :param network_segment_id: UUID representing the network segment

        """

        return self._delete(self.network_segment_path % network_segment_id)

**** CubicPower OpenStack Study ****

    def create_logical_network(self, network_profile, tenant_id):

        """

        Create a logical network on the VSM.

        :param network_profile: network profile dict

        :param tenant_id: UUID representing the tenant

        """

        LOG.debug(_("Logical network"))

        body = {'description': network_profile['name'],

                'tenantId': tenant_id}

        logical_network_name = (network_profile['id'] +

                                c_const.LOGICAL_NETWORK_SUFFIX)

        return self._post(self.logical_network_path % logical_network_name,

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_logical_network(self, logical_network_name):

        """

        Delete a logical network on VSM.

        :param logical_network_name: string representing name of the logical

                                     network

        """

        return self._delete(

            self.logical_network_path % logical_network_name)

**** CubicPower OpenStack Study ****

    def create_network_segment_pool(self, network_profile, tenant_id):

        """

        Create a network segment pool on the VSM.

        :param network_profile: network profile dict

        :param tenant_id: UUID representing the tenant

        """

        LOG.debug(_("network_segment_pool"))

        logical_network_name = (network_profile['id'] +

                                c_const.LOGICAL_NETWORK_SUFFIX)

        body = {'name': network_profile['name'],

                'description': network_profile['name'],

                'id': network_profile['id'],

                'logicalNetwork': logical_network_name,

                'tenantId': tenant_id}

        return self._post(

            self.network_segment_pool_path % network_profile['id'],

            body=body)

**** CubicPower OpenStack Study ****

    def update_network_segment_pool(self, network_profile):

        """

        Update a network segment pool on the VSM.

        :param network_profile: network profile dict

        """

        body = {'name': network_profile['name'],

                'description': network_profile['name']}

        return self._post(self.network_segment_pool_path %

                          network_profile['id'], body=body)

**** CubicPower OpenStack Study ****

    def delete_network_segment_pool(self, network_segment_pool_id):

        """

        Delete a network segment pool on the VSM.

        :param network_segment_pool_id: UUID representing the network

                                        segment pool

        """

        return self._delete(self.network_segment_pool_path %

                            network_segment_pool_id)

**** CubicPower OpenStack Study ****

    def create_ip_pool(self, subnet):

        """

        Create an ip-pool on the VSM.

        :param subnet: subnet dict

        """

        if subnet['cidr']:

            try:

                ip = netaddr.IPNetwork(subnet['cidr'])

                netmask = str(ip.netmask)

                network_address = str(ip.network)

            except netaddr.AddrFormatError:

                msg = _("Invalid input for CIDR")

                raise n_exc.InvalidInput(error_message=msg)

        else:

            netmask = network_address = ""

        if subnet['allocation_pools']:

            address_range_start = subnet['allocation_pools'][0]['start']

            address_range_end = subnet['allocation_pools'][0]['end']

        else:

            address_range_start = None

            address_range_end = None

        body = {'addressRangeStart': address_range_start,

                'addressRangeEnd': address_range_end,

                'ipAddressSubnet': netmask,

                'description': subnet['name'],

                'gateway': subnet['gateway_ip'],

                'dhcp': subnet['enable_dhcp'],

                'dnsServersList': subnet['dns_nameservers'],

                'networkAddress': network_address,

                'tenantId': subnet['tenant_id']}

        return self._post(self.ip_pool_path % subnet['id'],

                          body=body)

**** CubicPower OpenStack Study ****

    def update_ip_pool(self, subnet):

        """

        Update an ip-pool on the VSM.

        :param subnet: subnet dictionary

        """

        body = {'description': subnet['name'],

                'dhcp': subnet['enable_dhcp'],

                'dnsServersList': subnet['dns_nameservers']}

        return self._post(self.ip_pool_path % subnet['id'],

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_ip_pool(self, subnet_id):

        """

        Delete an ip-pool on the VSM.

        :param subnet_id: UUID representing the subnet

        """

        return self._delete(self.ip_pool_path % subnet_id)

**** CubicPower OpenStack Study ****

    def create_vm_network(self,

                          port,

                          vm_network_name,

                          policy_profile):

        """

        Create a VM network on the VSM.

        :param port: port dict

        :param vm_network_name: name of the VM network

        :param policy_profile: policy profile dict

        """

        body = {'name': vm_network_name,

                'networkSegmentId': port['network_id'],

                'networkSegment': port['network_id'],

                'portProfile': policy_profile['name'],

                'portProfileId': policy_profile['id'],

                'tenantId': port['tenant_id'],

                'portId': port['id'],

                'macAddress': port['mac_address'],

                }

        if port.get('fixed_ips'):

            body['ipAddress'] = port['fixed_ips'][0]['ip_address']

            body['subnetId'] = port['fixed_ips'][0]['subnet_id']

        return self._post(self.vm_networks_path,

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_vm_network(self, vm_network_name):

        """

        Delete a VM network on the VSM.

        :param vm_network_name: name of the VM network

        """

        return self._delete(self.vm_network_path % vm_network_name)

**** CubicPower OpenStack Study ****

    def create_n1kv_port(self, port, vm_network_name):

        """

        Create a port on the VSM.

        :param port: port dict

        :param vm_network_name: name of the VM network which imports this port

        """

        body = {'id': port['id'],

                'macAddress': port['mac_address']}

        if port.get('fixed_ips'):

            body['ipAddress'] = port['fixed_ips'][0]['ip_address']

            body['subnetId'] = port['fixed_ips'][0]['subnet_id']

        return self._post(self.ports_path % vm_network_name,

                          body=body)

**** CubicPower OpenStack Study ****

    def update_n1kv_port(self, vm_network_name, port_id, body):

        """

        Update a port on the VSM.

        Update the mac address associated with the port

        :param vm_network_name: name of the VM network which imports this port

        :param port_id: UUID of the port

        :param body: dict of the arguments to be updated

        """

        return self._post(self.port_path % (vm_network_name, port_id),

                          body=body)

**** CubicPower OpenStack Study ****

    def delete_n1kv_port(self, vm_network_name, port_id):

        """

        Delete a port on the VSM.

        :param vm_network_name: name of the VM network which imports this port

        :param port_id: UUID of the port

        """

        return self._delete(self.port_path % (vm_network_name, port_id))

**** CubicPower OpenStack Study ****

    def _do_request(self, method, action, body=None,

                    headers=None):

        """

        Perform the HTTP request.

        The response is in either XML format or plain text. A GET method will

        invoke a XML response while a PUT/POST/DELETE returns message from the

        VSM in plain text format.

        Exception is raised when VSM replies with an INTERNAL SERVER ERROR HTTP

        status code (500) i.e. an error has occurred on the VSM or SERVICE

        UNAVAILABLE (503) i.e. VSM is not reachable.

        :param method: type of the HTTP request. POST, GET, PUT or DELETE

        :param action: path to which the client makes request

        :param body: dict for arguments which are sent as part of the request

        :param headers: header for the HTTP request

        :returns: XML or plain text in HTTP response

        """

        action = self.action_prefix + action

        if not headers and self.hosts:

            headers = self._get_auth_header(self.hosts[0])

        headers['Content-Type'] = self._set_content_type('json')

        if body:

            body = self._serialize(body)

            LOG.debug(_("req: %s"), body)

        try:

            resp, replybody = (httplib2.Http(timeout=self.timeout).

                               request(action,

                                       method,

                                       body=body,

                                       headers=headers))

        except Exception as e:

            raise c_exc.VSMConnectionFailed(reason=e)

        LOG.debug(_("status_code %s"), resp.status)

        if resp.status == 200:

            if 'application/xml' in resp['content-type']:

                return self._deserialize(replybody, resp.status)

            elif 'text/plain' in resp['content-type']:

                LOG.debug(_("VSM: %s"), replybody)

        else:

            raise c_exc.VSMError(reason=replybody)

**** CubicPower OpenStack Study ****

    def _serialize(self, data):

        """

        Serialize a dictionary with a single key into either xml or json.

        :param data: data in the form of dict

        """

        if data is None:

            return None

        elif type(data) is dict:

            return wsgi.Serializer().serialize(data, self._set_content_type())

        else:

            raise Exception(_("Unable to serialize object of type = '%s'") %

                            type(data))

**** CubicPower OpenStack Study ****

    def _deserialize(self, data, status_code):

        """

        Deserialize an XML string into a dictionary.

        :param data: XML string from the HTTP response

        :param status_code: integer status code from the HTTP response

        :return: data in the form of dict

        """

        if status_code == 204:

            return data

        return wsgi.Serializer(self._serialization_metadata).deserialize(

            data, self._set_content_type('xml'))

**** CubicPower OpenStack Study ****

    def _set_content_type(self, format=None):

        """

        Set the mime-type to either 'xml' or 'json'.

        :param format: format to be set.

        :return: mime-type string

        """

        if not format:

            format = self.format

        return "application/%s" % format

**** CubicPower OpenStack Study ****

    def _delete(self, action, body=None, headers=None):

        return self._do_request("DELETE", action, body=body,

                                headers=headers)

**** CubicPower OpenStack Study ****

    def _get(self, action, body=None, headers=None):

        return self._do_request("GET", action, body=body,

                                headers=headers)

**** CubicPower OpenStack Study ****

    def _post(self, action, body=None, headers=None):

        return self._do_request("POST", action, body=body,

                                headers=headers)

**** CubicPower OpenStack Study ****

    def _put(self, action, body=None, headers=None):

        return self._do_request("PUT", action, body=body,

                                headers=headers)

**** CubicPower OpenStack Study ****

    def _get_vsm_hosts(self):

        """

        Retrieve a list of VSM ip addresses.

        :return: list of host ip addresses

        """

        return [cr[c_const.CREDENTIAL_NAME] for cr in

                network_db_v2.get_all_n1kv_credentials()]

**** CubicPower OpenStack Study ****

    def _get_auth_header(self, host_ip):

        """

        Retrieve header with auth info for the VSM.

        :param host_ip: IP address of the VSM

        :return: authorization header dict

        """

        username = c_cred.Store.get_username(host_ip)

        password = c_cred.Store.get_password(host_ip)

        auth = base64.encodestring("%s:%s" % (username, password))

        header = {"Authorization": "Basic %s" % auth}

        return header

**** CubicPower OpenStack Study ****

    def get_clusters(self):

        """Fetches a list of all vxlan gateway clusters."""

        return self._get(self.clusters_path)

**** CubicPower OpenStack Study ****

    def create_encapsulation_profile(self, encap):

        """

        Create an encapsulation profile on VSM.

        :param encap: encapsulation dict

        """

        body = {'name': encap['name'],

                'addMappings': encap['add_segment_list'],

                'delMappings': encap['del_segment_list']}

        return self._post(self.encap_profiles_path,

                          body=body)

**** CubicPower OpenStack Study ****

    def update_encapsulation_profile(self, context, profile_name, body):

        """

        Adds a vlan to bridge-domain mapping to an encapsulation profile.

        :param profile_name: Name of the encapsulation profile

        :param body: mapping dictionary

        """

        return self._post(self.encap_profile_path

                          % profile_name, body=body)

**** CubicPower OpenStack Study ****

    def delete_encapsulation_profile(self, name):

        """

        Delete an encapsulation profile on VSM.

        :param name: name of the encapsulation profile to be deleted

        """

        return self._delete(self.encap_profile_path % name)