Add python parsing library for network settings file. 89/12689/2
authorFeng Pan <fpan@redhat.com>
Fri, 22 Apr 2016 22:49:07 +0000 (18:49 -0400)
committerFeng Pan <fpan@redhat.com>
Thu, 5 May 2016 03:48:00 +0000 (23:48 -0400)
Changes:
   - Implements network_settings.yaml file parsing in python.
   - Adds support for both IPv4 and IPv6 in network_settings.yaml
   - Adds support for api_network in network_settings.yaml
   - Removes bash library functions for network related functions.
   - Adds dependency to python34-yaml for apex-common package.

Note that support for ipv6 and api_network is not complete yet.
Proper configuriration of network environment and nic template
files will be added later.

Change-Id: I087f725dabedfef109c9de1f58ce2611da647e87
Signed-off-by: Feng Pan <fpan@redhat.com>
build/opnfv-apex-common.spec
ci/deploy.sh
lib/common-functions.sh
lib/python/apex-python-utils.py [new file with mode: 0755]
lib/python/apex/__init__.py
lib/python/apex/ip_utils.py
lib/python/apex/net_env.py [new file with mode: 0644]

index 89fb403..56f0f30 100644 (file)
@@ -11,7 +11,7 @@ Source0:      opnfv-apex-common.tar.gz
 BuildArch:     noarch
 BuildRequires: python-docutils python34-devel
 Requires:      openstack-tripleo opnfv-apex-sdn opnfv-apex-undercloud openvswitch qemu-kvm bridge-utils libguestfs-tools
-Requires:      initscripts net-tools iputils iproute iptables python34
+Requires:      initscripts net-tools iputils iproute iptables python34 python34-yaml
 
 %description
 Scripts for OPNFV deployment using RDO Manager
@@ -39,12 +39,14 @@ install config/deploy/os-onos-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv
 install config/deploy/os-opencontrail-nofeature-ha.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/os-opencontrail-nofeature-ha.yaml
 install config/network/network_settings.yaml %{buildroot}%{_sysconfdir}/opnfv-apex/network_settings.yaml
 
-mkdir -p %{buildroot}%{_var}/opt/opnfv/lib/
+mkdir -p %{buildroot}%{_var}/opt/opnfv/lib/python/apex
 install lib/common-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/
 install lib/utility-functions.sh %{buildroot}%{_var}/opt/opnfv/lib/
+install lib/python/apex-python-utils.py %{buildroot}%{_var}/opt/opnfv/lib/python/
 mkdir -p %{buildroot}%{python3_sitelib}/apex/
 install lib/python/apex/__init__.py %{buildroot}%{python3_sitelib}/apex/
 install lib/python/apex/ip_utils.py %{buildroot}%{python3_sitelib}/apex/
+install lib/python/apex/net_env.py %{buildroot}%{python3_sitelib}/apex/
 mkdir -p %{buildroot}%{_var}/opt/opnfv/lib/installer/onos/
 install lib/installer/onos/onos_gw_mac_update.sh %{buildroot}%{_var}/opt/opnfv/lib/installer/onos/
 
@@ -64,7 +66,11 @@ install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/
 %attr(755,root,root) %{_bindir}/opnfv-util
 %{_var}/opt/opnfv/lib/common-functions.sh
 %{_var}/opt/opnfv/lib/utility-functions.sh
+%{_var}/opt/opnfv/lib/python/apex-python-utils.py
+%{_var}/opt/opnfv/lib/python/apex-python-utils.pyo
+%{_var}/opt/opnfv/lib/python/apex-python-utils.pyc
 %{python3_sitelib}/apex/ip_utils.py
+%{python3_sitelib}/apex/net_env.py
 %{python3_sitelib}/apex/__init__.py
 %{python3_sitelib}/apex/__pycache__/*
 %{_var}/opt/opnfv/lib/installer/onos/onos_gw_mac_update.sh
@@ -84,6 +90,8 @@ install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/
 %doc %{_docdir}/opnfv/inventory.yaml.example
 
 %changelog
+* Fri Apr 22 2016 Feng Pan <fpan@redhat.com> - 3.0-3
+- Adds python network setting parsing lib.
 * Fri Apr 15 2016 Feng Pan <fpan@redhat.com> - 3.0-2
 - Adds python ip utility lib.
 * Mon Apr 11 2016 Tim Rozet <trozet@redhat.com> - 3.0-1
index ebcde33..cd76558 100755 (executable)
@@ -101,126 +101,13 @@ parse_setting_value() {
 }
 ##parses network settings yaml into globals
 parse_network_settings() {
-  local required_network_settings="cidr"
-  local common_optional_network_settings="usable_ip_range"
-  local admin_network_optional_settings="provisioner_ip dhcp_range introspection_range"
-  local public_network_optional_settings="floating_ip_range gateway provisioner_ip"
-  local nic_value cidr
-
-  eval $(parse_yaml ${NETSETS})
-  for network in ${OPNFV_NETWORK_TYPES}; do
-    if [[ $(eval echo \${${network}_enabled}) == 'true' ]]; then
-      enabled_network_list+="${network} "
-    elif [ "${network}" == 'admin_network' ]; then
-      echo -e "${red}ERROR: You must enable admin_network and configure it explicitly or use auto-detection${reset}"
-      exit 1
-    elif [[ "${network}" == 'public_network' && "$net_isolation_enabled" == "TRUE" ]]; then
-      echo -e "${red}ERROR: You must enable public_network and configure it explicitly or use auto-detection${reset}"
+  if output=$(python3.4 -B $CONFIG/lib/python/apex-python-utils.py parse_net_settings -n $NETSETS -i $net_isolation_enabled); then
+      eval "$output"
+      echo -e "${blue}${output}${reset}"
+  else
       exit 1
-    else
-      echo -e "${blue}INFO: Network: ${network} is disabled, will collapse into admin_network"
-    fi
-  done
+  fi
 
-  # check for enabled network values
-  for enabled_network in ${enabled_network_list}; do
-    # detect required settings first to continue
-    echo -e "${blue}INFO: Detecting Required settings for: ${enabled_network}${reset}"
-    for setting in ${required_network_settings}; do
-      eval "setting_value=\${${enabled_network}_${setting}}"
-      if [ -z "${setting_value}" ]; then
-        # if setting is missing we try to autodetect
-        eval "nic_value=\${${enabled_network}_bridged_interface}"
-        if [ -n "$nic_value" ]; then
-          setting_value=$(eval find_${setting} ${nic_value})
-          if [ -n "$setting_value" ]; then
-            eval "${enabled_network}_${setting}=${setting_value}"
-            echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
-          else
-            echo -e "${red}ERROR: Auto-detection failed: ${setting} not found using interface: ${nic_value}${reset}"
-            exit 1
-          fi
-        else
-          echo -e "${red}ERROR: Required setting: ${setting} not found, and bridge interface not provided\
-for Auto-detection${reset}"
-          exit 1
-        fi
-      else
-        echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
-      fi
-    done
-    echo -e "${blue}INFO: Detecting Common settings for: ${enabled_network}${reset}"
-    # detect optional common settings
-    # these settings can be auto-generated if missing
-    for setting in ${common_optional_network_settings}; do
-      eval "setting_value=\${${enabled_network}_${setting}}"
-      if [ -z "${setting_value}" ]; then
-        if [ -n "$nic_value" ]; then
-          setting_value=$(eval find_${setting} ${nic_value})
-        else
-          setting_value=''
-          echo -e "${blue}INFO: Skipping Auto-detection, NIC not specified for ${enabled_network}.  Attempting Auto-generation...${reset}"
-        fi
-        if [ -n "$setting_value" ]; then
-          eval "${enabled_network}_${setting}=${setting_value}"
-          echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
-        else
-          # if Auto-detection fails we can auto-generate with CIDR
-          eval "cidr=\${${enabled_network}_cidr}"
-          if [ -n "$cidr" ]; then
-            echo -e "${blue}INFO: Auto-generating: ${setting}${reset}"
-            setting_value=$(eval generate_${setting} ${cidr})
-          else
-            setting_value=''
-            echo -e "${red}ERROR: Auto-generation failed: required parameter CIDR missing for network ${enabled_network}${reset}"
-          fi
-          if [ -n "$setting_value" ]; then
-            eval "${enabled_network}_${setting}=${setting_value}"
-            echo -e "${blue}INFO: Auto-generated: ${enabled_network}_${setting}: ${setting_value}${reset}"
-          else
-            echo -e "${red}ERROR: Auto-generation failed: ${setting} not found${reset}"
-            exit 1
-          fi
-        fi
-      else
-        echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
-      fi
-    done
-    echo -e "${blue}INFO: Detecting Network Specific settings for: ${enabled_network}${reset}"
-    # detect network specific settings
-    for setting in $(eval echo \${${enabled_network}_optional_settings}); do
-      eval "setting_value=\${${enabled_network}_${setting}}"
-      if [ -z "${setting_value}" ]; then
-        if [ -n "$nic_value" ]; then
-          setting_value=$(eval find_${setting} ${nic_value})
-        else
-          setting_value=''
-          echo -e "${blue}INFO: Skipping Auto-detection, NIC not specified for ${enabled_network}.  Attempting Auto-generation...${reset}"
-        fi
-        if [ -n "$setting_value" ]; then
-          eval "${enabled_network}_${setting}=${setting_value}"
-          echo -e "${blue}INFO: Auto-detection: ${enabled_network}_${setting}: ${setting_value}${reset}"
-        else
-          eval "cidr=\${${enabled_network}_cidr}"
-          if [ -n "$cidr" ]; then
-            setting_value=$(eval generate_${setting} ${cidr})
-          else
-            setting_value=''
-            echo -e "${red}ERROR: Auto-generation failed: required parameter CIDR missing for network ${enabled_network}${reset}"
-          fi
-          if [ -n "$setting_value" ]; then
-            eval "${enabled_network}_${setting}=${setting_value}"
-            echo -e "${blue}INFO: Auto-generated: ${enabled_network}_${setting}: ${setting_value}${reset}"
-          else
-            echo -e "${red}ERROR: Auto-generation failed: ${setting} not found${reset}"
-            exit 1
-          fi
-        fi
-      else
-        echo -e "${blue}INFO: ${enabled_network}_${setting}: ${setting_value}${reset}"
-      fi
-    done
-  done
 }
 ##parses deploy settings yaml into globals and options array
 ##params: none
@@ -686,7 +573,7 @@ function configure_network_environment {
   sed -i '/EC2MetadataIp/c\\  EC2MetadataIp: '${admin_network_provisioner_ip}'' $1
 
   # check for private network
-  if [[ ! -z "$private_network_enabled" && "$private_network_enabled" == "true" ]]; then
+  if [[ ! -z "$private_network_enabled" && "$private_network_enabled" == "True" ]]; then
       sed -i 's#^.*Network::Tenant.*$#  OS::TripleO::Network::Tenant: '${tht_dir}'/tenant.yaml#' $1
       sed -i 's#^.*Controller::Ports::TenantPort:.*$#  OS::TripleO::Controller::Ports::TenantPort: '${tht_dir}'/ports/tenant.yaml#' $1
       sed -i 's#^.*Compute::Ports::TenantPort:.*$#  OS::TripleO::Compute::Ports::TenantPort: '${tht_dir}'/ports/tenant.yaml#' $1
@@ -700,7 +587,7 @@ function configure_network_environment {
   fi
 
   # check for storage network
-  if [[ ! -z "$storage_network_enabled" && "$storage_network_enabled" == "true" ]]; then
+  if [[ ! -z "$storage_network_enabled" && "$storage_network_enabled" == "True" ]]; then
       sed -i 's#^.*Network::Storage:.*$#  OS::TripleO::Network::Storage: '${tht_dir}'/storage.yaml#' $1
       sed -i 's#^.*Network::Ports::StorageVipPort:.*$#  OS::TripleO::Network::Ports::StorageVipPort: '${tht_dir}'/ports/storage.yaml#' $1
       sed -i 's#^.*Controller::Ports::StoragePort:.*$#  OS::TripleO::Controller::Ports::StoragePort: '${tht_dir}'/ports/storage.yaml#' $1
index 32ee6bc..e7041ac 100644 (file)
@@ -2,9 +2,6 @@
 # Common Functions used by  OPNFV Apex
 # author: Tim Rozet (trozet@redhat.com)
 
-#python ip_gen command
-ip_gen="python3.4 -B -m apex.ip_utils generate_ip_range"
-
 ##converts subnet mask to prefix
 ##params: subnet mask
 function prefix2mask {
@@ -17,324 +14,11 @@ function prefix2mask {
 ##find ip of interface
 ##params: interface name
 function find_ip {
-  ip addr show $1 | grep -Eo '^\s+inet\s+[\.0-9]+' | awk '{print $2}'
-}
-
-##finds subnet of ip and netmask
-##params: ip, netmask
-function find_subnet {
-  IFS=. read -r i1 i2 i3 i4 <<< "$1"
-  IFS=. read -r m1 m2 m3 m4 <<< "$2"
-  printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))"
-}
-
-##verify subnet has at least n IPs
-##params: subnet mask, n IPs
-function verify_subnet_size {
-  IFS=. read -r i1 i2 i3 i4 <<< "$1"
-  num_ips_required=$2
-
-  ##this function assumes you would never need more than 254
-  ##we check here to make sure
-  if [ "$num_ips_required" -ge 254 ]; then
-    echo -e "\n\n${red}ERROR: allocating more than 254 IPs is unsupported...Exiting${reset}\n\n"
-    return 1
-  fi
-
-  ##we just return if 3rd octet is not 255
-  ##because we know the subnet is big enough
-  if [ "$i3" -ne 255 ]; then
-    return 0
-  elif [ $((254-$i4)) -ge "$num_ips_required" ]; then
-    return 0
-  else
-    echo -e "\n\n${red}ERROR: Subnet is too small${reset}\n\n"
-    return 1
-  fi
-}
-
-##finds last usable ip (broadcast minus 1) of a subnet from an IP and netmask
-## Warning: This function only works for IPv4 at the moment.
-##params: ip, netmask
-function find_last_ip_subnet {
-  IFS=. read -r i1 i2 i3 i4 <<< "$1"
-  IFS=. read -r m1 m2 m3 m4 <<< "$2"
-  IFS=. read -r s1 s2 s3 s4 <<< "$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).$((i4 & m4))"
-  printf "%d.%d.%d.%d\n" "$((255 - $m1 + $s1))" "$((255 - $m2 + $s2))" "$((255 - $m3 + $s3))" "$((255 - $m4 + $s4 - 1))"
-}
-
-##increments subnet by a value
-##params: ip, value
-##assumes low value
-function increment_subnet {
-  IFS=. read -r i1 i2 i3 i4 <<< "$1"
-  printf "%d.%d.%d.%d\n" "$i1" "$i2" "$i3" "$((i4 | $2))"
-}
-
-##finds netmask of interface
-##params: interface
-##returns long format 255.255.x.x
-function find_netmask {
-  ifconfig $1 | grep -Eo 'netmask\s+[\.0-9]+' | awk '{print $2}'
-}
-
-##finds short netmask of interface
-##params: interface
-##returns short format, ex: /21
-function find_short_netmask {
-  echo "/$(ip addr show $1 | grep -Eo '^\s+inet\s+[\/\.0-9]+' | awk '{print $2}' | cut -d / -f2)"
-}
-
-##increments next IP
-##params: ip
-##assumes a /24 subnet
-function next_ip {
-  baseaddr="$(echo $1 | cut -d. -f1-3)"
-  lsv="$(echo $1 | cut -d. -f4)"
-  if [ "$lsv" -ge 254 ]; then
-    return 1
-  fi
-  ((lsv++))
-  echo $baseaddr.$lsv
-}
-
-##subtracts a value from an IP address
-##params: last ip, ip_count
-##assumes ip_count is less than the last octect of the address
-subtract_ip() {
-  IFS=. read -r i1 i2 i3 i4 <<< "$1"
-  ip_count=$2
-  if [ $i4 -lt $ip_count ]; then
-    echo -e "\n\n${red}ERROR: Can't subtract $ip_count from IP address $1  Exiting${reset}\n\n"
-    exit 1
-  fi
-  printf "%d.%d.%d.%d\n" "$i1" "$i2" "$i3" "$((i4 - $ip_count ))"
-}
-
-##check if IP is in use
-##params: ip
-##ping ip to get arp entry, then check arp
-function is_ip_used {
-  ping -c 5 $1 > /dev/null 2>&1
-  arp -n | grep "$1 " | grep -iv incomplete > /dev/null 2>&1
-}
-
-##find next usable IP
-##params: ip
-function next_usable_ip {
-  new_ip=$(next_ip $1)
-  while [ "$new_ip" ]; do
-    if ! is_ip_used $new_ip; then
-      echo $new_ip
-      return 0
-    fi
-    new_ip=$(next_ip $new_ip)
-  done
-  return 1
-}
-
-##increment ip by value
-##params: ip, amount to increment by
-##increment_ip $next_private_ip 10
-function increment_ip {
-  baseaddr="$(echo $1 | cut -d. -f1-3)"
-  lsv="$(echo $1 | cut -d. -f4)"
-  incrval=$2
-  lsv=$((lsv+incrval))
-  if [ "$lsv" -ge 254 ]; then
-    return 1
-  fi
-  echo $baseaddr.$lsv
-}
-
-##finds gateway on system
-##params: interface to validate gateway on (optional)
-##find_gateway em1
-function find_gateway {
-  local gw gw_interface
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  gw=$(ip route | grep default | awk '{print $3}')
-  gw_interface=$(ip route get $gw | awk '{print $3}')
-  if [ -n "$1" ]; then
-    if [ "$gw_interface" == "$1" ]; then
-      echo ${gw}
-    fi
-  fi
-}
-
-##finds subnet in CIDR notation for interface
-##params: interface to find CIDR
-function find_cidr {
-  local cidr network ip netmask short_mask
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  ip=$(find_ip $1)
-  netmask=$(find_netmask $1)
-  if [[ -z "$ip" || -z "$netmask" ]]; then
-    return 1
-  fi
-  network=$(find_subnet ${ip} ${netamsk})
-  short_mask=$(find_short_netmask $1)
-  if [[ -z "$network" || -z "$short_mask" ]]; then
-    return 1
-  fi
-  cidr="${subnet}'\'${short_mask}"
-  echo ${cidr}
-}
-
-##finds block of usable IP addresses for an interface
-##simply returns at the moment the correct format
-##after first 20 IPs, and leave 20 IPs at end of subnet (for floating ips, etc)
-##params: interface to find IP
-function find_usable_ip_range {
-  local interface_ip subnet_mask first_block_ip last_block_ip
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  interface_ip=$(find_ip $1)
-  subnet_mask=$(find_netmask $1)
-  if [[ -z "$interface_ip" || -z "$subnet_mask" ]]; then
-    return 1
-  fi
-  interface_ip=$(increment_ip ${interface_ip} 20)
-  first_block_ip=$(next_usable_ip ${interface_ip})
-  if [ -z "$first_block_ip" ]; then
-    return 1
-  fi
-  last_block_ip=$(find_last_ip_subnet ${interface_ip} ${subnet_mask})
-  if [ -z "$last_block_ip" ]; then
-    return 1
-  else
-    last_block_ip=$(subtract_ip ${last_block_ip} 21)
-    echo "${first_block_ip},${last_block_ip}"
-  fi
-
-}
-
-##generates usable IP range in correct format based on CIDR
-##A block of 20 IP addresses are reserved at beginning of address space.
-##A block of 22 IP addresses are reserved at end of address space, this includes
-##the broadcast IP address.
-##In a /24 IPv4 CIDR, this results in .1-20 as as .234-255 being excluded.
-##params: cidr
-function generate_usable_ip_range {
-  if [ -z "$1" ]; then
-    return 1
-  fi
-  echo $($ip_gen $1 21 -23)
-}
-
-
-##find the undercloud IP address
-##finds first usable IP on subnet
-##params: interface
-function find_provisioner_ip {
-  local interface_ip
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  interface_ip=$(find_ip $1)
-  if [ -z "$interface_ip" ]; then
-    return 1
-  fi
-  echo $(increment_ip ${interface_ip} 1)
-}
-
-##generates undercloud IP address based on CIDR
-##params: cidr
-function generate_provisioner_ip {
-  if [ -z "$1" ]; then
-    return 1
-  fi
-  echo $($ip_gen $1 1 1)
-}
-
-
-##finds the dhcp range available via interface
-##uses first 8 IPs, after 2nd IP
-##params: interface
-function find_dhcp_range {
-  local dhcp_range_start dhcp_range_end interface_ip
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  interface_ip=$(find_ip $1)
-  if [ -z "$interface_ip" ]; then
-    return 1
-  fi
-  dhcp_range_start=$(increment_ip ${interface_ip} 2)
-  dhcp_range_end=$(increment_ip ${dhcp_range_start} 8)
-  echo "${dhcp_range_start},${dhcp_range_end}"
-}
-
-##generates the dhcp range available via CIDR
-##uses first 8 IPs, after 1st IP
-##params: cidr
-function generate_dhcp_range {
-  if [ -z "$1" ]; then
-    return 1
-  fi
-  echo $($ip_gen $1 2 10)
-}
-
-##finds the introspection range available via interface
-##uses 8 IPs, after the first 10 IPs
-##params: interface
-function find_introspection_range {
-  local inspect_range_start inspect_range_end interface_ip
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  interface_ip=$(find_ip $1)
-  if [ -z "$interface_ip" ]; then
-    return 1
-  fi
-  inspect_range_start=$(increment_ip ${interface_ip} 10)
-  inspect_range_end=$(increment_ip ${inspect_range_start} 8)
-  echo "${inspect_range_start},${inspect_range_end}"
-}
-
-##generate the introspection range available via CIDR
-##uses 8 IPs, after the first 10 IPs
-##params: cidr
-function generate_introspection_range {
-  if [ -z "$1" ]; then
-    return 1
-  fi
-  echo $($ip_gen $1 11 19)
-}
-
-##finds the floating ip range available via interface
-##uses last 20 IPs of a subnet, minus last IP
-##params: interface
-function find_floating_ip_range {
-  local float_range_start float_range_end interface_ip subnet_mask
-  if [ -z "$1"  ]; then
-    return 1
-  fi
-  interface_ip=$(find_ip $1)
-  subnet_mask=$(find_netmask $1)
-  if [[ -z "$interface_ip" || -z "$subnet_mask" ]]; then
+  if [[ -z "$1" ]]; then
     return 1
   fi
-  float_range_end=$(find_last_ip_subnet ${interface_ip} ${subnet_mask})
-  float_range_end=$(subtract_ip ${float_range_end} 1)
-  float_range_start=$(subtract_ip ${float_range_end} 19)
-  echo "${float_range_start},${float_range_end}"
-}
 
-##generate the floating range available via CIDR
-##uses last 20 IPs of subnet, minus last 2 IPs.
-##In a /24 IPv4 CIDR, this would result in floating ip range of .234-253
-##params: cidr
-function generate_floating_ip_range {
-  if [ -z "$1" ]; then
-    return 1
-  fi
-  echo $($ip_gen $1 -22 -3)
+  python3.4 -B $CONFIG/lib/python/apex-python-utils.py find_ip -i $1
 }
 
 ##attach interface to OVS and set the network config correctly
diff --git a/lib/python/apex-python-utils.py b/lib/python/apex-python-utils.py
new file mode 100755 (executable)
index 0000000..802e857
--- /dev/null
@@ -0,0 +1,67 @@
+##############################################################################
+# Copyright (c) 2016 Feng Pan (fpan@redhat.com)
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+import argparse
+import sys
+import apex
+import logging
+import os
+
+def parse_net_settings(settings_args):
+    settings = apex.NetworkSettings(settings_args.path,
+                                    settings_args.network_isolation)
+    settings.dump_bash()
+
+
+def find_ip(int_args):
+    interface = apex.ip_utils.get_interface(int_args.interface,
+                                      int_args.address_family)
+    if interface:
+        print(interface.ip)
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--DEBUG', action='store_true', default=False,
+                    help="Turn on debug messages")
+subparsers = parser.add_subparsers()
+
+net_settings = subparsers.add_parser('parse_net_settings',
+                                     help='Parse network settings file')
+net_settings.add_argument('-n', '--path', default='network_settings.yaml',
+                          help='path to network settings file')
+net_settings.add_argument('-i', '--network_isolation', type=bool, default=True,
+                          help='network isolation')
+net_settings.set_defaults(func=parse_net_settings)
+
+get_int_ip = subparsers.add_parser('find_ip',
+                                   help='Find interface ip')
+get_int_ip.add_argument('-i', '--interface', required=True,
+                        help='Interface name')
+get_int_ip.add_argument('-af', '--address_family', default=4, type=int,
+                        choices=[4, 6],
+                        help='IP Address family')
+get_int_ip.set_defaults(func=find_ip)
+
+args = parser.parse_args(sys.argv[1:])
+if args.DEBUG:
+    logging.basicConfig(level=logging.DEBUG)
+else:
+    apex_log_filename = '/var/log/apex/apex.log'
+    os.makedirs(os.path.dirname(apex_log_filename), exist_ok=True)
+    logging.basicConfig(filename=apex_log_filename,
+                        format='%(asctime)s %(levelname)s: %(message)s',
+                        datefmt='%m/%d/%Y %I:%M:%S %p',
+                        level=logging.DEBUG)
+
+if hasattr(args, 'func'):
+    args.func(args)
+else:
+    parser.print_help()
+    exit(1)
index 0c0ae6c..88b066b 100644 (file)
@@ -7,3 +7,5 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
+
+from .net_env import NetworkSettings
index 680ce7e..d7099db 100644 (file)
@@ -1,4 +1,3 @@
-
 ##############################################################################
 # Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others.
 #
 
 
 import ipaddress
+import subprocess
+import re
+import logging
+
+
+def get_ip_range(start_offset=None, count=None, end_offset=None,
+                 cidr=None, interface=None):
+    """
+    Generate IP range for a network (cidr) or an interface.
+
+    If CIDR is provided, it will take precedence over interface. In this case,
+    The entire CIDR IP address space is considered usable. start_offset will be
+    calculated from the network address, and end_offset will be calculated from
+    the last address in subnet.
+
+    If interface is provided, the interface IP will be used to calculate
+    offsets:
+        - If the interface IP is in the first half of the address space,
+        start_offset will be calculated from the interface IP, and end_offset
+        will be calculated from end of address space.
+        - If the interface IP is in the second half of the address space,
+        start_offset will be calculated from the network address in the address
+        space, and end_offset will be calculated from the interface IP.
+
+    2 of start_offset, end_offset and count options must be provided:
+        - If start_offset and end_offset are provided, a range from start_offset
+        to end_offset will be returned.
+        - If count is provided, a range from either start_offset to (start_offset
+        +count) or (end_offset-count) to end_offset will be returned. The
+        IP range returned will be of size <count>.
+    Both start_offset and end_offset must be greater than 0.
+
+    Returns IP range in the format of "first_addr,second_addr" or exception
+    is raised.
+    """
+    if cidr:
+        if count and start_offset and not end_offset:
+            start_index = start_offset
+            end_index = start_offset + count -1
+        elif count and end_offset and not start_offset:
+            end_index = -1 - end_offset
+            start_index = -1 - end_index - count + 1
+        elif start_offset and end_offset and not count:
+            start_index = start_offset
+            end_index = -1 - end_offset
+        else:
+            raise IPUtilsException("Argument error: must pass in exactly 2 of"
+                                   "start_offset, end_offset and count")
+
+        start_ip = cidr[start_index]
+        end_ip = cidr[end_index]
+        network = cidr
+    elif interface:
+        network = interface.network
+        number_of_addr = network.num_addresses
+        if interface.ip < network[int(number_of_addr / 2)]:
+            if count and start_offset and not end_offset:
+                start_ip = interface.ip + start_offset
+                end_ip = start_ip + count - 1
+            elif count and end_offset and not start_offset:
+                end_ip = network[-1 - end_offset]
+                start_ip = end_ip - count + 1
+            elif start_offset and end_offset and not count:
+                start_ip = interface.ip + start_offset
+                end_ip = network[-1 - end_offset]
+            else:
+                raise IPUtilsException(
+                    "Argument error: must pass in exactly 2 of"
+                    "start_offset, end_offset and count")
+        else:
+            if count and start_offset and not end_offset:
+                start_ip = network[start_offset]
+                end_ip = start_ip + count -1
+            elif count and end_offset and not start_offset:
+                end_ip = interface.ip - end_offset
+                start_ip = end_ip - count + 1
+            elif start_offset and end_offset and not count:
+                start_ip = network[start_offset]
+                end_ip = interface.ip - end_offset
+            else:
+                raise IPUtilsException(
+                    "Argument error: must pass in exactly 2 of"
+                    "start_offset, end_offset and count")
+
+    else:
+        raise IPUtilsException("Must pass in cidr or interface to generate"
+                               "ip range")
+
+    range_result = _validate_ip_range(start_ip, end_ip, network)
+    if range_result:
+        ip_range = "{},{}".format(start_ip, end_ip)
+        return ip_range
+    else:
+        raise IPUtilsException("Invalid IP range: {},{} for network {}"
+                               .format(start_ip, end_ip, network))
+
+
+def get_ip(offset, cidr=None, interface=None):
+    """
+    Returns an IP in a network given an offset.
+
+    Either cidr or interface must be provided, cidr takes precedence.
+
+    If cidr is provided, offset is calculated from network address.
+    If interface is provided, offset is calculated from interface IP.
+
+    offset can be positive or negative, but the resulting IP address must also
+    be contained in the same subnet, otherwise an exception will be raised.
+
+    returns a IP address object.
+    """
+    if cidr:
+        ip = cidr[0 + offset]
+        network = cidr
+    elif interface:
+        ip = interface.ip + offset
+        network = interface.network
+    else:
+        raise IPUtilsException("Must pass in cidr or interface to generate IP")
+
+    if ip not in network:
+        raise IPUtilsException("IP {} not in network {}".format(ip, network))
+    else:
+        return str(ip)
 
 
 def generate_ip_range(args):
@@ -22,7 +145,8 @@ def generate_ip_range(args):
     start_position: starting index, default to first address in subnet (1)
     end_position:  ending index, default to last address in subnet (-1)
 
-    Returns IP range in string format. A single IP is returned if start and end IPs are identical.
+    Returns IP range in string format. A single IP is returned if start and
+    end IPs are identical.
     """
     cidr = ipaddress.ip_network(args.CIDR)
     (start_index, end_index) = (args.start_position, args.end_position)
@@ -32,23 +156,95 @@ def generate_ip_range(args):
         return ','.join(sorted([str(cidr[start_index]), str(cidr[end_index])]))
 
 
-def main():
-    import argparse
-    import sys
+def get_interface(nic, address_family=4):
+    """
+    Returns interface object for a given NIC name in the system
+
+    Only global address will be returned at the moment.
+
+    Returns interface object if an address is found for the given nic,
+    otherwise returns None.
+    """
+    if not nic.strip():
+        logging.error("empty nic name specified")
+        return None
+    output = subprocess.getoutput("ip -{} addr show {} scope global"
+                                  .format(address_family, nic))
+    if address_family == 4:
+        pattern = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}")
+    elif address_family == 6:
+        pattern = re.compile("([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}")
+    else:
+        raise IPUtilsException("Invalid address family: {}"
+                               .format(address_family))
+    match = re.search(pattern, output)
+    if match:
+        logging.info("found interface {} ip: {}".format(nic, match.group()))
+        return ipaddress.ip_interface(match.group())
+    else:
+        logging.info("interface ip not found! ip address output:\n{}"
+                        .format(output))
+        return None
+
+
+def find_gateway(interface):
+    """
+    Validate gateway on the system
+
+    Ensures that the provided interface object is in fact configured as default
+    route on the system.
+
+    Returns gateway IP (reachable from interface) if default route is found,
+    otherwise returns None.
+    """
+
+    address_family = interface.version
+    output = subprocess.getoutput("ip -{} route".format(address_family))
 
-    parser = argparse.ArgumentParser()
-    subparsers = parser.add_subparsers()
+    pattern = re.compile("default\s+via\s+(\S+)\s+")
+    match = re.search(pattern, output)
 
-    parser_gen_ip_range = subparsers.add_parser('generate_ip_range', help='Generate IP Range given CIDR')
-    parser_gen_ip_range.add_argument('CIDR', help='Network in CIDR notation')
-    parser_gen_ip_range.add_argument('start_position', type=int, help='Starting index')
-    parser_gen_ip_range.add_argument('end_position', type=int, help='Ending index')
-    parser_gen_ip_range.set_defaults(func=generate_ip_range)
+    if match:
+        gateway_ip = match.group(1)
+        reverse_route_output = subprocess.getoutput("ip route get {}"
+                                                    .format(gateway_ip))
+        pattern = re.compile("{}.+src\s+{}".format(gateway_ip, interface.ip))
+        if not re.search(pattern, reverse_route_output):
+            logging.warning("Default route doesn't match interface specified: "
+                            "{}".format(reverse_route_output))
+            return None
+        else:
+            return gateway_ip
+    else:
+        logging.warning("Can't find gateway address on system")
+        return None
+
+
+def _validate_ip_range(start_ip, end_ip, cidr):
+    """
+    Validates an IP range is in good order and the range is part of cidr.
+
+    Returns True if validation succeeds, False otherwise.
+    """
+    ip_range = "{},{}".format(start_ip, end_ip)
+    if end_ip <= start_ip:
+        logging.warning("IP range {} is invalid: end_ip should be greater than "
+                        "starting ip".format(ip_range))
+        return False
+    if start_ip not in ipaddress.ip_network(cidr):
+        logging.warning('start_ip {} is not in network {}'
+                        .format(start_ip, cidr))
+        return False
+    if end_ip not in ipaddress.ip_network(cidr):
+        logging.warning('end_ip {} is not in network {}'.format(end_ip, cidr))
+        return False
 
-    args = parser.parse_args(sys.argv[1:])
-    print(args.func(args))
+    return True
 
 
-if __name__ == '__main__':
-    main()
+class IPUtilsException(Exception):
+    def __init__(self, value):
+        self.value = value
 
+    def __str__(self):
+        return self.value
diff --git a/lib/python/apex/net_env.py b/lib/python/apex/net_env.py
new file mode 100644 (file)
index 0000000..ec46fe2
--- /dev/null
@@ -0,0 +1,242 @@
+##############################################################################
+# Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+import yaml
+import logging
+import ipaddress
+from . import ip_utils
+
+
+ADMIN_NETWORK = 'admin_network'
+PRIVATE_NETWORK = 'private_network'
+PUBLIC_NETWORK = 'public_network'
+STORAGE_NETWORK = 'storage_network'
+API_NETWORK = 'api_network'
+OPNFV_NETWORK_TYPES = [ADMIN_NETWORK, PRIVATE_NETWORK, PUBLIC_NETWORK,
+                       STORAGE_NETWORK, API_NETWORK]
+
+
+class NetworkSettings:
+    """
+    This class parses APEX network settings yaml file into an object. It
+    generates or detects all missing fields for deployment.
+
+    The resulting object will be used later to generate network environment file
+    as well as configuring post deployment networks.
+
+    Currently the parsed object is dumped into a bash global definition file
+    for deploy.sh consumption. This object will later be used directly as
+    deployment script move to python.
+    """
+    def __init__(self, filename, network_isolation):
+        with open(filename, 'r') as network_settings_file:
+            self.settings_obj = yaml.load(network_settings_file)
+            self.network_isolation = network_isolation
+            self.enabled_network_list = []
+            self._validate_input()
+
+    def _validate_input(self):
+        """
+        Validates the network settings file and populates all fields.
+
+        NetworkSettingsException will be raised if validation fails.
+        """
+        if ADMIN_NETWORK not in self.settings_obj or \
+                self.settings_obj[ADMIN_NETWORK].get('enabled') != True:
+            raise NetworkSettingsException("You must enable admin_network "
+                                           "and configure it explicitly or "
+                                           "use auto-detection")
+        if self.network_isolation and \
+            (PUBLIC_NETWORK not in self.settings_obj or
+                self.settings_obj[PUBLIC_NETWORK].get('enabled') != True):
+            raise NetworkSettingsException("You must enable public_network "
+                                           "and configure it explicitly or "
+                                           "use auto-detection")
+
+        for network in OPNFV_NETWORK_TYPES:
+            if network in self.settings_obj:
+                if self.settings_obj[network].get('enabled') == True:
+                    logging.info("{} enabled".format(network))
+                    self._config_required_settings(network)
+                    self._config_ip_range(network=network,
+                                          setting='usable_ip_range',
+                                          start_offset=21, end_offset=21)
+                    self._config_optional_settings(network)
+                    self.enabled_network_list.append(network)
+                else:
+                    logging.info("{} disabled, will collapse with "
+                                 "admin_network".format(network))
+            else:
+                logging.info("{} is not in specified, will collapse with "
+                             "admin_network".format(network))
+
+    def _config_required_settings(self, network):
+        """
+        Configures either CIDR or bridged_interface setting
+
+        cidr takes precedence if both cidr and bridged_interface are specified
+        for a given network.
+
+        When using bridged_interface, we will detect network setting on the
+        given NIC in the system. The resulting config in settings object will
+        be an ipaddress.network object, replacing the NIC name.
+        """
+        cidr = self.settings_obj[network].get('cidr')
+        nic_name = self.settings_obj[network].get('bridged_interface')
+
+        if cidr:
+            cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
+            self.settings_obj[network]['cidr'] = cidr
+            logging.info("{}_cidr: {}".format(network, cidr))
+            return 0
+        elif nic_name:
+            # If cidr is not specified, we need to know if we should find
+            # IPv6 or IPv4 address on the interface
+            if self.settings_obj[network].get('ipv6') == True:
+                address_family = 6
+            else:
+                address_family = 4
+            nic_interface = ip_utils.get_interface(nic_name, address_family)
+            if nic_interface:
+                self.settings_obj[network]['bridged_interface'] = nic_interface
+                logging.info("{}_bridged_interface: {}".
+                             format(network, nic_interface))
+                return 0
+            else:
+                raise NetworkSettingsException("Auto detection failed for {}: "
+                                               "Unable to find valid ip for "
+                                               "interface {}"
+                                               .format(network, nic_name))
+
+        else:
+            raise NetworkSettingsException("Auto detection failed for {}: "
+                                           "either bridge_interface or cidr "
+                                           "must be specified"
+                                           .format(network))
+
+    def _config_ip_range(self, network, setting, start_offset=None,
+                         end_offset=None, count=None):
+        """
+        Configures IP range for a given setting.
+
+        If the setting is already specified, no change will be made.
+
+        The spec for start_offset, end_offset and count are identical to
+        ip_utils.get_ip_range.
+        """
+        ip_range = self.settings_obj[network].get(setting)
+        interface = self.settings_obj[network].get('bridged_interface')
+
+        if not ip_range:
+            cidr = self.settings_obj[network].get('cidr')
+            ip_range = ip_utils.get_ip_range(start_offset=start_offset,
+                                             end_offset=end_offset,
+                                             count=count,
+                                             cidr=cidr,
+                                             interface=interface)
+            self.settings_obj[network][setting] = ip_range
+
+        logging.info("{}_{}: {}".format(network, setting, ip_range))
+
+    def _config_ip(self, network, setting, offset):
+        """
+        Configures IP for a given setting.
+
+        If the setting is already specified, no change will be made.
+
+        The spec for offset is identical to ip_utils.get_ip
+        """
+        ip = self.settings_obj[network].get(setting)
+        interface = self.settings_obj[network].get('bridged_interface')
+
+        if not ip:
+            cidr = self.settings_obj[network].get('cidr')
+            ip = ip_utils.get_ip(offset, cidr, interface)
+            self.settings_obj[network][setting] = ip
+
+        logging.info("{}_{}: {}".format(network, setting, ip))
+
+    def _config_optional_settings(self, network):
+        """
+        Configures optional settings:
+        - admin_network:
+            - provisioner_ip
+            - dhcp_range
+            - introspection_range
+        - public_network:
+            - provisioner_ip
+            - floating_ip
+            - gateway
+        """
+        if network == ADMIN_NETWORK:
+            self._config_ip(network, 'provisioner_ip', 1)
+            self._config_ip_range(network=network, setting='dhcp_range',
+                                  start_offset=2, count=9)
+            self._config_ip_range(network=network,
+                                  setting='introspection_range',
+                                  start_offset=11, count=9)
+        elif network == PUBLIC_NETWORK:
+            self._config_ip(network, 'provisioner_ip', 1)
+            self._config_ip_range(network=network,
+                                  setting='floating_ip',
+                                  end_offset=2, count=20)
+            self._config_gateway(network)
+
+    def _config_gateway(self, network):
+        """
+        Configures gateway setting for a given network.
+
+        If cidr is specified, we always use the first address in the address
+        space for gateway. Otherwise, we detect the system gateway.
+        """
+        gateway = self.settings_obj[network].get('gateway')
+        interface = self.settings_obj[network].get('bridged_interface')
+
+        if not gateway:
+            cidr = self.settings_obj[network].get('cidr')
+            if cidr:
+                gateway = ip_utils.get_ip(1, cidr)
+            else:
+                gateway = ip_utils.find_gateway(interface)
+
+            if gateway:
+                self.settings_obj[network]['gateway'] = gateway
+            else:
+                raise NetworkSettingsException("Failed to set gateway")
+
+        logging.info("{}_gateway: {}".format(network, gateway))
+
+
+    def dump_bash(self, path=None):
+        """
+        Prints settings for bash consumption.
+
+        If optional path is provided, bash string will be written to the file
+        instead of stdout.
+        """
+        bash_str = ''
+        for network in self.enabled_network_list:
+            for key, value in self.settings_obj[network].items():
+                bash_str += "{}_{}={}\n".format(network, key, value)
+        bash_str += "enabled_network_list='{}'\n" \
+            .format(' '.join(self.enabled_network_list))
+        if path:
+            with open(path, 'w') as file:
+                file.write(bash_str)
+        else:
+            print(bash_str)
+
+
+class NetworkSettingsException(Exception):
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+            return self.value