--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Implement the plugging for OpendayLight openflow controller
+It creates the class OF_conn to create dataplane connections
+with static rules based on packet destination MAC address
+'''
+
+__author__="Pablo Montes, Alfonso Tierno"
+__date__ ="$28-oct-2014 12:07:15$"
+
+
+import json
+import requests
+import base64
+import logging
+
+class OF_conn():
+ '''OpenDayLight connector. No MAC learning is used'''
+ def __init__(self, params):
+ ''' Constructor.
+ Params: dictionary with the following keys:
+ of_dpid: DPID to use for this controller
+ of_ip: controller IP address
+ of_port: controller TCP port
+ of_user: user credentials, can be missing or None
+ of_password: password credentials
+ of_debug: debug level for logging. Default to ERROR
+ other keys are ignored
+ Raise an exception if same parameter is missing or wrong
+ '''
+ #check params
+ if "of_ip" not in params or params["of_ip"]==None or "of_port" not in params or params["of_port"]==None:
+ raise ValueError("IP address and port must be provided")
+ #internal variables
+ self.name = "OpenDayLight"
+ self.headers = {'content-type':'application/json',
+ 'Accept':'application/json'
+ }
+ self.auth=None
+ self.pp2ofi={} # From Physical Port to OpenFlow Index
+ self.ofi2pp={} # From OpenFlow Index to Physical Port
+
+ self.dpid = str(params["of_dpid"])
+ self.id = 'openflow:'+str(int(self.dpid.replace(':', ''), 16))
+ self.url = "http://%s:%s" %( str(params["of_ip"]), str(params["of_port"] ) )
+ if "of_user" in params and params["of_user"]!=None:
+ if not params.get("of_password"):
+ of_password=""
+ else:
+ of_password=str(params["of_password"])
+ self.auth = base64.b64encode(str(params["of_user"])+":"+of_password)
+ self.headers['Authorization'] = 'Basic '+self.auth
+
+
+ self.logger = logging.getLogger('vim.OF.ODL')
+ self.logger.setLevel( getattr(logging, params.get("of_debug", "ERROR")) )
+
+ def get_of_switches(self):
+ ''' Obtain a a list of switches or DPID detected by this controller
+ Return
+ >=0, list: list length, and a list where each element a tuple pair (DPID, IP address)
+ <0, text_error: if fails
+ '''
+ try:
+ of_response = requests.get(self.url+"/restconf/operational/opendaylight-inventory:nodes",
+ headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("get_of_switches " + error_text)
+ return -1 , error_text
+ self.logger.debug("get_of_switches " + error_text)
+ info = of_response.json()
+
+ if type(info) != dict:
+ self.logger.error("get_of_switches. Unexpected response, not a dict: %s", str(info))
+ return -1, "Unexpected response, not a dict. Wrong version?"
+
+ nodes = info.get('nodes')
+ if type(nodes) is not dict:
+ self.logger.error("get_of_switches. Unexpected response at 'nodes', not found or not a dict: %s", str(type(info)))
+ return -1, "Unexpected response at 'nodes', not found or not a dict. Wrong version?"
+
+ node_list = nodes.get('node')
+ if type(node_list) is not list:
+ self.logger.error("get_of_switches. Unexpected response, at 'nodes':'node', not found or not a list: %s", str(type(node_list)))
+ return -1, "Unexpected response, at 'nodes':'node', not found or not a list. Wrong version?"
+
+ switch_list=[]
+ for node in node_list:
+ node_id = node.get('id')
+ if node_id is None:
+ self.logger.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'id', not found: %s", str(node))
+ return -1, "Unexpected response at 'nodes':'node'[]:'id', not found . Wrong version?"
+
+ if node_id == 'controller-config':
+ continue
+
+ node_ip_address = node.get('flow-node-inventory:ip-address')
+ if node_ip_address is None:
+ self.logger.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'flow-node-inventory:ip-address', not found: %s", str(node))
+ return -1, "Unexpected response at 'nodes':'node'[]:'flow-node-inventory:ip-address', not found. Wrong version?"
+
+ node_id_hex=hex(int(node_id.split(':')[1])).split('x')[1].zfill(16)
+ switch_list.append( (':'.join(a+b for a,b in zip(node_id_hex[::2], node_id_hex[1::2])), node_ip_address))
+
+ return len(switch_list), switch_list
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("get_of_switches " + error_text)
+ return -1, error_text
+
+ def obtain_port_correspondence(self):
+ '''Obtain the correspondence between physical and openflow port names
+ return:
+ 0, dictionary: with physical name as key, openflow name as value
+ -1, error_text: if fails
+ '''
+ try:
+ of_response = requests.get(self.url+"/restconf/operational/opendaylight-inventory:nodes",
+ headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("obtain_port_correspondence " + error_text)
+ return -1 , error_text
+ self.logger.debug("obtain_port_correspondence " + error_text)
+ info = of_response.json()
+
+ if type(info) != dict:
+ self.logger.error("obtain_port_correspondence. Unexpected response not a dict: %s", str(info))
+ return -1, "Unexpected openflow response, not a dict. Wrong version?"
+
+ nodes = info.get('nodes')
+ if type(nodes) is not dict:
+ self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes', not found or not a dict: %s", str(type(nodes)))
+ return -1, "Unexpected response at 'nodes',not found or not a dict. Wrong version?"
+
+ node_list = nodes.get('node')
+ if type(node_list) is not list:
+ self.logger.error("obtain_port_correspondence. Unexpected response, at 'nodes':'node', not found or not a list: %s", str(type(node_list)))
+ return -1, "Unexpected response, at 'nodes':'node', not found or not a list. Wrong version?"
+
+ for node in node_list:
+ node_id = node.get('id')
+ if node_id is None:
+ self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:'id', not found: %s", str(node))
+ return -1, "Unexpected response at 'nodes':'node'[]:'id', not found . Wrong version?"
+
+ if node_id == 'controller-config':
+ continue
+
+ # Figure out if this is the appropriate switch. The 'id' is 'openflow:' plus the decimal value
+ # of the dpid
+ # In case this is not the desired switch, continue
+ if self.id != node_id:
+ continue
+
+ node_connector_list = node.get('node-connector')
+ if type(node_connector_list) is not list:
+ self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:'node-connector', not found or not a list: %s", str(node))
+ return -1, "Unexpected response at 'nodes':'node'[]:'node-connector', not found or not a list. Wrong version?"
+
+ for node_connector in node_connector_list:
+ self.pp2ofi[ str(node_connector['flow-node-inventory:name']) ] = str(node_connector['id'] )
+ self.ofi2pp[ node_connector['id'] ] = str(node_connector['flow-node-inventory:name'])
+
+
+ node_ip_address = node.get('flow-node-inventory:ip-address')
+ if node_ip_address is None:
+ self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:'flow-node-inventory:ip-address', not found: %s", str(node))
+ return -1, "Unexpected response at 'nodes':'node'[]:'flow-node-inventory:ip-address', not found. Wrong version?"
+ self.ip_address = node_ip_address
+
+ #If we found the appropriate dpid no need to continue in the for loop
+ break
+
+ #print self.name, ": obtain_port_correspondence ports:", self.pp2ofi
+ return 0, self.pp2ofi
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("obtain_port_correspondence " + error_text)
+ return -1, error_text
+
+ def get_of_rules(self, translate_of_ports=True):
+ ''' Obtain the rules inserted at openflow controller
+ Params:
+ translate_of_ports: if True it translates ports from openflow index to physical switch name
+ Return:
+ 0, dict if ok: with the rule name as key and value is another dictionary with the following content:
+ priority: rule priority
+ name: rule name (present also as the master dict key)
+ ingress_port: match input port of the rule
+ dst_mac: match destination mac address of the rule, can be missing or None if not apply
+ vlan_id: match vlan tag of the rule, can be missing or None if not apply
+ actions: list of actions, composed by a pair tuples:
+ (vlan, None/int): for stripping/setting a vlan tag
+ (out, port): send to this port
+ switch: DPID, all
+ -1, text_error if fails
+ '''
+
+ if len(self.ofi2pp) == 0:
+ r,c = self.obtain_port_correspondence()
+ if r<0:
+ return r,c
+ #get rules
+ try:
+ of_response = requests.get(self.url+"/restconf/config/opendaylight-inventory:nodes/node/" + self.id +
+ "/table/0", headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+
+ # The configured page does not exist if there are no rules installed. In that case we return an empty dict
+ if of_response.status_code == 404:
+ return 0, {}
+
+ elif of_response.status_code != 200:
+ self.logger.warning("get_of_rules " + error_text)
+ return -1 , error_text
+ self.logger.debug("get_of_rules " + error_text)
+
+ info = of_response.json()
+
+ if type(info) != dict:
+ self.logger.error("get_of_rules. Unexpected response not a dict: %s", str(info))
+ return -1, "Unexpected openflow response, not a dict. Wrong version?"
+
+ table = info.get('flow-node-inventory:table')
+ if type(table) is not list:
+ self.logger.error("get_of_rules. Unexpected response at 'flow-node-inventory:table', not a list: %s", str(type(table)))
+ return -1, "Unexpected response at 'flow-node-inventory:table', not a list. Wrong version?"
+
+ flow_list = table[0].get('flow')
+ if flow_list is None:
+ return 0, {}
+
+ if type(flow_list) is not list:
+ self.logger.error("get_of_rules. Unexpected response at 'flow-node-inventory:table'[0]:'flow', not a list: %s", str(type(flow_list)))
+ return -1, "Unexpected response at 'flow-node-inventory:table'[0]:'flow', not a list. Wrong version?"
+
+ #TODO translate ports according to translate_of_ports parameter
+
+ rules = dict()
+ for flow in flow_list:
+ if not ('id' in flow and 'match' in flow and 'instructions' in flow and \
+ 'instruction' in flow['instructions'] and 'apply-actions' in flow['instructions']['instruction'][0] and \
+ 'action' in flow['instructions']['instruction'][0]['apply-actions']):
+ return -1, "unexpected openflow response, one or more elements are missing. Wrong version?"
+
+ flow['instructions']['instruction'][0]['apply-actions']['action']
+
+ rule = dict()
+ rule['switch'] = self.dpid
+ rule['priority'] = flow.get('priority')
+ #rule['name'] = flow['id']
+ #rule['cookie'] = flow['cookie']
+ if 'in-port' in flow['match']:
+ in_port = flow['match']['in-port']
+ if not in_port in self.ofi2pp:
+ return -1, "Error: Ingress port "+in_port+" is not in switch port list"
+
+ if translate_of_ports:
+ in_port = self.ofi2pp[in_port]
+
+ rule['ingress_port'] = in_port
+
+ if 'vlan-match' in flow['match'] and 'vlan-id' in flow['match']['vlan-match'] and \
+ 'vlan-id' in flow['match']['vlan-match']['vlan-id'] and \
+ 'vlan-id-present' in flow['match']['vlan-match']['vlan-id'] and \
+ flow['match']['vlan-match']['vlan-id']['vlan-id-present'] == True:
+ rule['vlan_id'] = flow['match']['vlan-match']['vlan-id']['vlan-id']
+
+ if 'ethernet-match' in flow['match'] and 'ethernet-destination' in flow['match']['ethernet-match'] and \
+ 'address' in flow['match']['ethernet-match']['ethernet-destination']:
+ rule['dst_mac'] = flow['match']['ethernet-match']['ethernet-destination']['address']
+
+ instructions=flow['instructions']['instruction'][0]['apply-actions']['action']
+
+ max_index=0
+ for instruction in instructions:
+ if instruction['order'] > max_index:
+ max_index = instruction['order']
+
+ actions=[None]*(max_index+1)
+ for instruction in instructions:
+ if 'output-action' in instruction:
+ if not 'output-node-connector' in instruction['output-action']:
+ return -1, "unexpected openflow response, one or more elementa are missing. Wrong version?"
+
+ out_port = instruction['output-action']['output-node-connector']
+ if not out_port in self.ofi2pp:
+ return -1, "Error: Output port "+out_port+" is not in switch port list"
+
+ if translate_of_ports:
+ out_port = self.ofi2pp[out_port]
+
+ actions[instruction['order']] = ('out',out_port)
+
+ elif 'strip-vlan-action' in instruction:
+ actions[instruction['order']] = ('vlan', None)
+
+ elif 'set-field' in instruction:
+ if not ('vlan-match' in instruction['set-field'] and 'vlan-id' in instruction['set-field']['vlan-match'] and 'vlan-id' in instruction['set-field']['vlan-match']['vlan-id']):
+ return -1, "unexpected openflow response, one or more elements are missing. Wrong version?"
+
+ actions[instruction['order']] = ('vlan', instruction['set-field']['vlan-match']['vlan-id']['vlan-id'])
+
+ actions = [x for x in actions if x != None]
+
+ rule['actions'] = list(actions)
+ rules[flow['id']] = dict(rule)
+
+ #flow['id']
+ #flow['priority']
+ #flow['cookie']
+ #flow['match']['in-port']
+ #flow['match']['vlan-match']['vlan-id']['vlan-id']
+ # match -> in-port
+ # -> vlan-match -> vlan-id -> vlan-id
+ #flow['match']['vlan-match']['vlan-id']['vlan-id-present']
+ #TODO se asume que no se usan reglas con vlan-id-present:false
+ #instructions -> instruction -> apply-actions -> action
+ #instructions=flow['instructions']['instruction'][0]['apply-actions']['action']
+ #Es una lista. Posibles elementos:
+ #max_index=0
+ #for instruction in instructions:
+ # if instruction['order'] > max_index:
+ # max_index = instruction['order']
+ #actions=[None]*(max_index+1)
+ #for instruction in instructions:
+ # if 'output-action' in instruction:
+ # actions[instruction['order']] = ('out',instruction['output-action']['output-node-connector'])
+ # elif 'strip-vlan-action' in instruction:
+ # actions[instruction['order']] = ('vlan', None)
+ # elif 'set-field' in instruction:
+ # actions[instruction['order']] = ('vlan', instruction['set-field']['vlan-match']['vlan-id']['vlan-id'])
+ #
+ #actions = [x for x in actions if x != None]
+ # -> output-action -> output-node-connector
+ # -> pop-vlan-action
+
+ return 0, rules
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("get_of_rules " + error_text)
+ return -1, error_text
+
+ def del_flow(self, flow_name):
+ ''' Delete an existing rule
+ Params: flow_name, this is the rule name
+ Return
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ try:
+ of_response = requests.delete(self.url+"/restconf/config/opendaylight-inventory:nodes/node/" + self.id +
+ "/table/0/flow/"+flow_name, headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("del_flow " + error_text)
+ return -1 , error_text
+ self.logger.debug("del_flow OK " + error_text)
+ return 0, None
+
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("del_flow " + error_text)
+ return -1, error_text
+
+ def new_flow(self, data):
+ ''' Insert a new static rule
+ Params: data: dictionary with the following content:
+ priority: rule priority
+ name: rule name
+ ingress_port: match input port of the rule
+ dst_mac: match destination mac address of the rule, missing or None if not apply
+ vlan_id: match vlan tag of the rule, missing or None if not apply
+ actions: list of actions, composed by a pair tuples with these posibilities:
+ ('vlan', None/int): for stripping/setting a vlan tag
+ ('out', port): send to this port
+ Return
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ if len(self.pp2ofi) == 0:
+ r,c = self.obtain_port_correspondence()
+ if r<0:
+ return r,c
+ try:
+ #We have to build the data for the opendaylight call from the generic data
+ sdata = dict()
+ sdata['flow-node-inventory:flow'] = list()
+ sdata['flow-node-inventory:flow'].append(dict())
+ flow = sdata['flow-node-inventory:flow'][0]
+ flow['id'] = data['name']
+ flow['flow-name'] = data['name']
+ flow['idle-timeout'] = 0
+ flow['hard-timeout'] = 0
+ flow['table_id'] = 0
+ flow['priority'] = data.get('priority')
+ flow['match'] = dict()
+ if not data['ingress_port'] in self.pp2ofi:
+ error_text = 'Error. Port '+data['ingress_port']+' is not present in the switch'
+ self.logger.warning("new_flow " + error_text)
+ return -1, error_text
+ flow['match']['in-port'] = self.pp2ofi[data['ingress_port']]
+ if 'dst_mac' in data:
+ flow['match']['ethernet-match'] = dict()
+ flow['match']['ethernet-match']['ethernet-destination'] = dict()
+ flow['match']['ethernet-match']['ethernet-destination']['address'] = data['dst_mac']
+ if data.get('vlan_id'):
+ flow['match']['vlan-match'] = dict()
+ flow['match']['vlan-match']['vlan-id'] = dict()
+ flow['match']['vlan-match']['vlan-id']['vlan-id-present'] = True
+ flow['match']['vlan-match']['vlan-id']['vlan-id'] = int(data['vlan_id'])
+ flow['instructions'] = dict()
+ flow['instructions']['instruction'] = list()
+ flow['instructions']['instruction'].append(dict())
+ flow['instructions']['instruction'][0]['order'] = 1
+ flow['instructions']['instruction'][0]['apply-actions'] = dict()
+ flow['instructions']['instruction'][0]['apply-actions']['action'] = list()
+ actions = flow['instructions']['instruction'][0]['apply-actions']['action']
+
+ order = 0
+ for action in data['actions']:
+ new_action = { 'order': order }
+ if action[0] == "vlan":
+ if action[1] == None:
+ #strip vlan
+ new_action['strip-vlan-action'] = dict()
+ else:
+ new_action['set-field'] = dict()
+ new_action['set-field']['vlan-match'] = dict()
+ new_action['set-field']['vlan-match']['vlan-id'] = dict()
+ new_action['set-field']['vlan-match']['vlan-id']['vlan-id-present'] = True
+ new_action['set-field']['vlan-match']['vlan-id']['vlan-id'] = int(action[1])
+ elif action[0] == 'out':
+ new_action['output-action'] = dict()
+ if not action[1] in self.pp2ofi:
+ error_msj = 'Port '+action[1]+' is not present in the switch'
+ return -1, error_msj
+
+ new_action['output-action']['output-node-connector'] = self.pp2ofi[ action[1] ]
+ else:
+ error_msj = "Unknown item '%s' in action list" % action[0]
+ self.logger.error("new_flow " + error_msj)
+ return -1, error_msj
+
+ actions.append(new_action)
+ order += 1
+
+ #print json.dumps(sdata)
+ of_response = requests.put(self.url+"/restconf/config/opendaylight-inventory:nodes/node/" + self.id +
+ "/table/0/flow/" + data['name'],
+ headers=self.headers, data=json.dumps(sdata) )
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("new_flow " + error_text)
+ return -1 , error_text
+ self.logger.debug("new_flow OK " + error_text)
+ return 0, None
+
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("new_flow " + error_text)
+ return -1, error_text
+
+ def clear_all_flows(self):
+ ''' Delete all existing rules
+ Return:
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ try:
+ of_response = requests.delete(self.url+"/restconf/config/opendaylight-inventory:nodes/node/" + self.id +
+ "/table/0", headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200 and of_response.status_code != 404: #HTTP_Not_Found
+ self.logger.warning("clear_all_flows " + error_text)
+ return -1 , error_text
+ self.logger.debug("clear_all_flows OK " + error_text)
+ return 0, None
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("clear_all_flows " + error_text)
+ return -1, error_text
--- /dev/null
+# Juju Charms for deploying OpenVIM
+
+## Overview
+These are the charm layers used to build Juju charms for deploying OpenVIM components. These charms are also published to the [Juju Charm Store](https://jujucharms.com/) and can be deployed directly from there using the [etsi-osm](https://jujucharms.com/u/nfv/etsi-osm), or they can be build from these layers and deployed locally.
+
+## Building the OpenVIM Charms
+
+To build these charms, you will need [charm-tools][]. You should also read
+over the developer [Getting Started][] page for an overview of charms and
+building them. Then, in any of the charm layer directories, use `charm build`.
+For example:
+
+ export JUJU_REPOSITORY=$HOME/charms
+ mkdir $HOME/charms
+
+ cd openvim/charms/hadoop/layer-openvim
+ charm build
+
+This will build the OpenVIM controller charm, pulling in the appropriate base and
+interface layers from [interfaces.juju.solutions][]. You can get local copies
+of those layers as well using `charm pull-source`:
+
+ export LAYER_PATH=$HOME/layers
+ export INTERFACE_PATH=$HOME/interfaces
+ mkdir $HOME/{layers,interfaces}
+
+ charm pull-source layer:openvim-compute
+ charm pull-source interface:openvim-compute
+
+You can then deploy the locally built charms individually:
+
+ juju deploy local:exenial/openvim
+
+You can also use the local version of a bundle:
+
+ juju deploy openvim/charms/bundles/openmano.yaml
+
+> Note: With Juju versions < 2.0, you will need to use [juju-deployer][] to
+deploy the local bundle.
+
+
+[charm-tools]: https://jujucharms.com/docs/stable/tools-charm-tools
+[Getting Started]: https://jujucharms.com/docs/devel/developer-getting-started
+[interfaces.juju.solutions]: http://interfaces.juju.solutions/
+[juju-deployer]: https://pypi.python.org/pypi/juju-deployer/
--- /dev/null
+series: xenial
+services:
+ mariadb:
+ charm: "cs:trusty/mariadb-3"
+ num_units: 1
+ annotations:
+ "gui-x": "539"
+ "gui-y": "494.7050807568877"
+ to:
+ - "0"
+ openmano:
+ charm: "cs:~nfv/openmano-3"
+ num_units: 1
+ annotations:
+ "gui-x": "939"
+ "gui-y": "494.7050807568877"
+ to:
+ - "1"
+ "openvim-controller":
+ charm: "cs:~nfv/openvim-controller-2"
+ num_units: 1
+ annotations:
+ "gui-x": "739"
+ "gui-y": "148.29491924311225"
+ to:
+ - "2"
+ "openvim-compute":
+ charm: "cs:~nfv/openvim-compute-2"
+ num_units: 1
+ annotations:
+ "gui-x": "339"
+ "gui-y": "148.29491924311225"
+ to:
+ - "3"
+relations:
+ - - "openmano:db"
+ - "mariadb:db"
+ - - "openvim-controller:db"
+ - "mariadb:db"
+ - - "openmano:openvim-controller"
+ - "openvim-controller:openvim-controller"
+ - - "openvim-controller:compute"
+ - "openvim-compute:compute"
+machines:
+ "0":
+ series: trusty
+ constraints: arch=amd64
+ "1":
+ series: xenial
+ constraints: arch=amd64
+ "2":
+ series: xenial
+ constraints: arch=amd64
+ "3":
+ series: xenial
+ constraints: arch=amd64
--- /dev/null
+name: openvim-compute
+version: 0
+description: Connection to an OpenVIM compute host
--- /dev/null
+from charms.reactive import hook
+from charms.reactive import RelationBase
+from charms.reactive import scopes
+
+
+class ProvidesOpenVIMCompute(RelationBase):
+ scope = scopes.GLOBAL
+
+ auto_accessors = ['ssh_key']
+
+ @hook('{provides:openvim-compute}-relation-{joined,changed}')
+ def changed(self):
+ self.set_state('{relation_name}.connected')
+ if self.ssh_key():
+ self.set_state('{relation_name}.available')
+
+ @hook('{provides:openvim-compute}-relation-{broken,departed}')
+ def departed(self):
+ self.remove_state('{relation_name}.connected')
+ self.remove_state('{relation_name}.available')
+
+ def ssh_key_installed(self):
+ convo = self.conversation()
+ convo.set_remote('ssh_key_installed', True)
+
+ def send_user(self, user):
+ convo = self.conversation()
+ convo.set_remote('user', user)
\ No newline at end of file
--- /dev/null
+from charms.reactive import hook
+from charms.reactive import RelationBase
+from charms.reactive import scopes
+
+
+class RequiresOpenVIMCompute(RelationBase):
+ scope = scopes.UNIT
+
+ @hook('{requires:openvim-compute}-relation-{joined,changed}')
+ def changed(self):
+ self.set_state('{relation_name}.connected')
+ if self.ready_to_ssh():
+ self.set_state('{relation_name}.available')
+
+ @hook('{requires:openvim-compute}-relation-{broken,departed}')
+ def departed(self):
+ self.remove_state('{relation_name}.connected')
+ self.remove_state('{relation_name}.available')
+
+ def send_ssh_key(self, key):
+ for c in self.conversations():
+ c.set_remote('ssh_key', key)
+
+ def authorized_nodes(self):
+ return [{
+ 'user': c.get_remote('user'),
+ 'address': c.get_remote('private-address'),
+ } for c in self.conversations() if c.get_remote('ssh_key_installed')]
+
+ def ready_to_ssh(self):
+ return len(self.authorized_nodes()) > 0
--- /dev/null
+name: openvim
+summary: Basic OpenVIM interface
+version: 1
--- /dev/null
+from charmhelpers.core import hookenv
+from charms.reactive import hook
+from charms.reactive import RelationBase
+from charms.reactive import scopes
+
+
+class OpenVimProvides(RelationBase):
+ scope = scopes.GLOBAL
+
+ @hook('{provides:openvim}-relation-{joined,changed}')
+ def changed(self):
+ self.set_state('{relation_name}.available')
+
+ @hook('{provides:openvim}-relation-{broken,departed}')
+ def broken(self):
+ self.remove_state('{relation_name}.available')
+
+ def configure(self, port, user):
+ relation_info = {
+ 'hostname': hookenv.unit_get('private-address'),
+ 'port': port,
+ 'user': user,
+ }
+ self.set_remote(**relation_info)
--- /dev/null
+from charms.reactive import hook
+from charms.reactive import RelationBase
+from charms.reactive import scopes
+
+
+class OpenVimRequires(RelationBase):
+ scope = scopes.UNIT
+
+ @hook('{requires:openvim}-relation-{joined,changed}')
+ def changed(self):
+ conv = self.conversation()
+ if conv.get_remote('port'):
+ # this unit's conversation has a port, so
+ # it is part of the set of available units
+ conv.set_state('{relation_name}.available')
+
+ @hook('{requires:openvim}-relation-{departed,broken}')
+ def broken(self):
+ conv = self.conversation()
+ conv.remove_state('{relation_name}.available')
+
+ def services(self):
+ """
+ Returns a list of available openvim services and their associated hosts
+ and ports.
+
+ The return value is a list of dicts of the following form::
+
+ [
+ {
+ 'service_name': name_of_service,
+ 'hosts': [
+ {
+ 'hostname': address_of_host,
+ 'port': port_for_host,
+ 'user': user_for_host,
+ },
+ # ...
+ ],
+ },
+ # ...
+ ]
+ """
+ services = {}
+ for conv in self.conversations():
+ service_name = conv.scope.split('/')[0]
+ service = services.setdefault(service_name, {
+ 'service_name': service_name,
+ 'hosts': [],
+ })
+ host = conv.get_remote('hostname') or \
+ conv.get_remote('private-address')
+ port = conv.get_remote('port')
+ user = conv.get_remote('user')
+ if host and port:
+ service['hosts'].append({
+ 'hostname': host,
+ 'port': port,
+ 'user': user,
+ })
+ return [s for s in services.values() if s['hosts']]
--- /dev/null
+# Overview
+
+Launches an OpenVIM compute node.
+
+# Preparation
+
+When running with an LXD cloud, the openvim-compute nodes needs to have some
+devices added and be run with extra privileges. A quick-and-dirty way of
+accomplishing this is to edit the juju-default LXD profile:
+
+ lxc profile edit juju-default
+
+change it to:
+
+ name: juju-default
+ config:
+ boot.autostart: "true"
+ security.nesting: "true"
+ security.privileged: "true"
+ description: ""
+ devices:
+ kvm:
+ path: /dev/kvm
+ type: unix-char
+ tun:
+ path: /dev/net/tun
+ type: unix-char
+
+# Usage
+
+ juju deploy mysql
+ juju deploy openvim-controller
+ juju deploy openvim-compute
+ juju relate mysql openvim-controller
+ juju relate openvim-compute openvim-controller
+
+# Creating and starting a VM
+
+The openvim-controller charm will create a default tenant, image, flavor,
+and networks, but you'll want to add your own VM when you're ready to deploy.
+This charm generates a basic VM yaml definition for you if you'd like to launch
+one quickly. First, ssh into your openvim-controller box:
+
+ juju ssh openvim-controller/0 # may not be zero, find instance id with `juju status`.
+
+Then create your VM and get its uuid:
+
+ /home/ubuntu/openmano/openvim/openvim vm-create /tmp/server.yaml
+
+And finally start it:
+
+ /home/ubuntu/openmano/openvim/openvim vm-start <vm-uuid>
+
+
+# Contact Information
+
+Rye Terrell rye.terrell@canonical.com
+George Kraft george.kraft@canonical.com
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="96"
+ height="96"
+ id="svg6517"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="icon.svg">
+ <defs
+ id="defs6519">
+ <linearGradient
+ id="Background-0">
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1"
+ offset="0"
+ id="stop4245" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.98039216"
+ offset="1"
+ id="stop4247" />
+ </linearGradient>
+ <linearGradient
+ id="Background">
+ <stop
+ id="stop4178"
+ offset="0"
+ style="stop-color:#b8b8b8;stop-opacity:1" />
+ <stop
+ id="stop4180"
+ offset="1"
+ style="stop-color:#c9c9c9;stop-opacity:1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Inner Shadow"
+ id="filter1121">
+ <feFlood
+ flood-opacity="0.59999999999999998"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood1123" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite1125" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur1127" />
+ <feOffset
+ dx="0"
+ dy="2"
+ result="offset"
+ id="feOffset1129" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite1131" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter950">
+ <feFlood
+ flood-opacity="0.25"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood952" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite954" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur956" />
+ <feOffset
+ dx="0"
+ dy="1"
+ result="offset"
+ id="feOffset958" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite960" />
+ </filter>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath873">
+ <g
+ transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
+ id="g875"
+ inkscape:label="Layer 1"
+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
+ <path
+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
+ d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
+ id="path877"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssssss" />
+ </g>
+ </clipPath>
+ <filter
+ inkscape:collect="always"
+ id="filter891"
+ inkscape:label="Badge Shadow">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.71999962"
+ id="feGaussianBlur893" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.0745362"
+ inkscape:cx="109.56798"
+ inkscape:cy="39.201101"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1600"
+ inkscape:window-height="876"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ showborder="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:showpageshadow="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid821" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="16,48"
+ id="guide823" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="64,80"
+ id="guide825" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="80,40"
+ id="guide827" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="64,16"
+ id="guide829" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6522">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="BACKGROUND"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(268,-635.29076)"
+ style="display:inline">
+ <path
+ style="display:inline;fill:#d9ffd9;fill-opacity:1;stroke:none;filter:url(#filter1121)"
+ d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 c -27.21517,0 -31.10302,-3.89189 -31.10302,-31.13514 z"
+ id="path6455"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssssss" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="PLACE YOUR PICTOGRAM HERE"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <image
+ y="19.840546"
+ x="15"
+ id="image3432"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA2MAAALTCAYAAACbjNrIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
+AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB
+VHic7N1pmFXVlTfw/9rn1sQkoDgBVQXibDSKsyYWQ40IMbGr4myMA4lJzNx5+83b3TdDJ51Od9JJ
+xyiTRAKFXXS6k6iFUIVFOo5RMhmSmKCC4ogiMlfde/Z6PwCGIEMN55y1zz7r96GfJ0/DOX+suvfs
+dfbeawNKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkop
+pZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWU
+UkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJK
+KaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSiml
+lFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRS
+SimllFJKKaWUUkoppZSzSDqAUiob8vm8qbz48ZOKCE+H5aMM0REgLmGLzUTUYwkvM9t1AJ5/OVd8
+KT9pZVE6s1LKIzVdufFDNxxbLBQribgKMMcAtpRghjHbAhu8bti8woZ+u+6cPz6NfN5KR1ZK+U+L
+MaVUbO5qbxwVlobvB9MHAL4QwNBe/tUigLUMftqweZoNP22JnsRhrz818+xVhRgjK6VSbuLEWSUb
+jh5yOiwmEtOJIJwE0AkAVwPI9fIyW0B4nBkduVzunmfvbXk+xshKqQzTYkylynfbG8ty1gwqGWwH
+B2GhlIvB0GIJghION3WT7eYdFduP2D5oc0vLklA6a5bNWlF/lmHOg9EEIIjw0jvA+BUDPwuMufeF
+h85/PK9vr5XKtua2oGp7eB6HfCkBl4BwFoDyCO9gCbgPxP+4tv3qX0d4XdVXzW1B5fbCMEslgwwX
+yqgYDDdAGObCLaHlnsDQtlI7YvuapU3d0lGV6i0txpTT2tqaSzeNfKuZQO8j8DnMqARgevFXQyK8
+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
+7sf8xOzDS8G8r+DxheC+L9/9B2kuUQFQhM1dK89QiB/0YIvkmiP5WXKbqmDTJD8u92Oy8KrkeY/+
+9OQQH2d3RIyVu+yOrssWZGt8nmJcYMHmST4zRptlwTL6RaHWL/dhMz8iWb/c8jF4b6bgvZ9a0fPi
+W+5HxLsOxRiQsLSKMQAAAIwvbOABAAAAACmgGAMAAACAFFCMAQAAAEAKKMYAAAAAIAUUYwAAAACQ
+AooxAAAAAEgBxRgAAAAApIBiDAAAAABSQDEGAAAAACmgGAMAAACAFFCMAQAAAEAKKMYAAAAAIAUU
+YwAAAACQAooxAAAAAEgBxRgAAAAApIBiDAAAAABSQDEGAAAAACmgGAMAAACAFFCMAQAAAEAKKMaA
+5I1USRsAAACoYhRjQMLctK3kNuQ9SWQBAABA9aIYAxLWV5v9omR/Ktme4q+2Peb6/PS6muuSTwYA
+AIBqYmkHACayc1f924IY9QELdpa717pUGxRmS5LcD0fTgMkG3Ao/s5H4RD639rmUIwMAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+APwflgAxUsonrkcAAAAASUVORK5CYII=
+"
+ preserveAspectRatio="none"
+ height="54.204151"
+ width="65" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="BADGE"
+ style="display:none"
+ sodipodi:insensitive="true">
+ <g
+ style="display:inline"
+ transform="translate(-340.00001,-581)"
+ id="g4394"
+ clip-path="none">
+ <g
+ id="g855">
+ <g
+ inkscape:groupmode="maskhelper"
+ id="g870"
+ clip-path="url(#clipPath873)"
+ style="opacity:0.6;filter:url(#filter891)">
+ <circle
+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
+ id="path844"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ </g>
+ <g
+ id="g862">
+ <circle
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ id="path4398"
+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ <circle
+ transform="matrix(1.25,0,0,1.25,33,-100.45273)"
+ id="path4400"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ <path
+ sodipodi:type="star"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ id="path4459"
+ sodipodi:sides="5"
+ sodipodi:cx="666.19574"
+ sodipodi:cy="589.50385"
+ sodipodi:r1="7.2431178"
+ sodipodi:r2="4.3458705"
+ sodipodi:arg1="1.0471976"
+ sodipodi:arg2="1.6755161"
+ inkscape:flatsided="false"
+ inkscape:rounded="0.1"
+ inkscape:randomized="0"
+ d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
--- /dev/null
+includes:
+ - 'layer:basic'
+ - 'interface:openvim-compute'
+options:
+ basic:
+ packages:
+ - qemu-kvm
+ - libvirt-bin
+ - bridge-utils
+ - virt-viewer
+ - virt-manager
+ - hugepages
--- /dev/null
+name: openvim-compute
+summary: Open Virtual Infrastructure Manager
+maintainers:
+ - Rye Terrell <rye.terrell@canonical.com>
+ - George Kraft <george.kraft@canonical.com>
+description: |
+ Installs and configures a compute node for OpenVIM
+tags:
+ - nfv
+ - telco
+ - osm
+series:
+ - xenial
+extra-bindings:
+ public:
+ internal:
+provides:
+ compute:
+ interface: openvim-compute
--- /dev/null
+from os import chmod
+from charms.reactive import when, when_not, set_state
+from charmhelpers.core.hookenv import status_set
+from charmhelpers.core.unitdata import kv
+from charmhelpers.core.host import mkdir, symlink, chownr, add_user_to_group
+from charmhelpers.fetch.archiveurl import ArchiveUrlFetchHandler
+from charmhelpers.contrib.unison import ensure_user
+
+def create_openvim_user():
+ status_set("maintenance", "Creating OpenVIM user")
+ ensure_user('openvim')
+
+def group_openvim_user():
+ status_set("maintenance", "Adding OpenVIM user to groups")
+ add_user_to_group('openvim', 'libvirtd')
+ add_user_to_group('openvim', 'sudo')
+
+def nopasswd_openvim_sudo():
+ status_set("maintenance", "Allowing nopasswd sudo for OpenVIM user")
+ with open('/etc/sudoers', 'r+') as f:
+ data = f.read()
+ if 'openvim ALL=(ALL) NOPASSWD:ALL' not in data:
+ f.seek(0)
+ f.truncate()
+ data += '\nopenvim ALL=(ALL) NOPASSWD:ALL\n'
+ f.write(data)
+
+def setup_qemu_binary():
+ status_set("maintenance", "Setting up qemu-kvm binary")
+ mkdir('/usr/libexec', owner='root', group='root', perms=0o775, force=False)
+ symlink('/usr/bin/kvm', '/usr/libexec/qemu-kvm')
+
+def setup_images_folder():
+ status_set("maintenance", "Setting up VM images folder")
+ mkdir('/opt/VNF', owner='openvim', group='openvim', perms=0o775, force=False)
+ symlink('/var/lib/libvirt/images', '/opt/VNF/images')
+ chownr('/opt/VNF', owner='openvim', group='openvim', follow_links=False, chowntopdir=True)
+ chownr('/var/lib/libvirt/images', owner='root', group='openvim', follow_links=False, chowntopdir=True)
+ chmod('/var/lib/libvirt/images', 0o775)
+
+def download_default_image():
+ status_set("maintenance", "Downloading default image")
+ fetcher = ArchiveUrlFetchHandler()
+ fetcher.download(
+ source="https://cloud-images.ubuntu.com/releases/16.04/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img",
+ dest="/opt/VNF/images/ubuntu-16.04-server-cloudimg-amd64-disk1.img"
+ # TODO: add checksum
+ )
+
+@when_not('openvim-compute.installed')
+def prepare_openvim_compute():
+ create_openvim_user()
+ group_openvim_user()
+ nopasswd_openvim_sudo()
+ setup_qemu_binary()
+ setup_images_folder()
+ download_default_image()
+ status_set("active", "Ready")
+ set_state('openvim-compute.installed')
+
+@when('compute.available', 'openvim-compute.installed')
+def install_ssh_key(compute):
+ cache = kv()
+ if cache.get("ssh_key:" + compute.ssh_key()):
+ return
+ mkdir('/home/openvim/.ssh', owner='openvim', group='openvim', perms=0o775)
+ with open("/home/openvim/.ssh/authorized_keys", 'a') as f:
+ f.write(compute.ssh_key() + '\n')
+ compute.ssh_key_installed()
+ cache.set("ssh_key:" + compute.ssh_key(), True)
+
+@when('compute.connected')
+def send_user(compute):
+ compute.send_user('openvim')
--- /dev/null
+# Overview
+
+Launches an OpenVIM controller.
+
+# Preparation
+
+When running with an LXD cloud, the openvim-compute nodes needs to have some
+devices added and be run with extra privileges. A quick-and-dirty way of
+accomplishing this is to edit the juju-default LXD profile:
+
+ lxc profile edit juju-default
+
+change it to:
+
+ name: juju-default
+ config:
+ boot.autostart: "true"
+ security.nesting: "true"
+ security.privileged: "true"
+ description: ""
+ devices:
+ kvm:
+ path: /dev/kvm
+ type: unix-char
+ tun:
+ path: /dev/net/tun
+ type: unix-char
+
+# Usage
+
+ juju deploy mysql
+ juju deploy openvim
+ juju deploy openvim-compute
+ juju relate mysql openvim
+ juju relate openvim-compute openvim
+
+# Creating and starting a VM
+
+The openvim charm will create a default tenant, image, flavor,
+and networks, but you'll want to add your own VM when you're ready to deploy.
+This charm generates a basic VM yaml definition for you if you'd like to launch
+one quickly. First, ssh into your openvim box:
+
+ juju ssh openvim-contrller/0 # may not be zero, find instance id with `juju status`.
+
+Then create your VM and get its uuid:
+
+ /home/ubuntu/openmano/openvim/openvim vm-create /tmp/server.yaml
+
+And finally start it:
+
+ /home/ubuntu/openmano/openvim/openvim vm-start <vm-uuid>
+
+
+# Contact Information
+
+Rye Terrell rye.terrell@canonical.com
+George Kraft george.kraft@canonical.com
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="96"
+ height="96"
+ id="svg6517"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="icon.svg">
+ <defs
+ id="defs6519">
+ <linearGradient
+ id="Background-0">
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1"
+ offset="0"
+ id="stop4245" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.98039216"
+ offset="1"
+ id="stop4247" />
+ </linearGradient>
+ <linearGradient
+ id="Background">
+ <stop
+ id="stop4178"
+ offset="0"
+ style="stop-color:#b8b8b8;stop-opacity:1" />
+ <stop
+ id="stop4180"
+ offset="1"
+ style="stop-color:#c9c9c9;stop-opacity:1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Inner Shadow"
+ id="filter1121">
+ <feFlood
+ flood-opacity="0.59999999999999998"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood1123" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite1125" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur1127" />
+ <feOffset
+ dx="0"
+ dy="2"
+ result="offset"
+ id="feOffset1129" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite1131" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter950">
+ <feFlood
+ flood-opacity="0.25"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood952" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite954" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur956" />
+ <feOffset
+ dx="0"
+ dy="1"
+ result="offset"
+ id="feOffset958" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite960" />
+ </filter>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath873">
+ <g
+ transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
+ id="g875"
+ inkscape:label="Layer 1"
+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
+ <path
+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
+ d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
+ id="path877"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssssss" />
+ </g>
+ </clipPath>
+ <filter
+ inkscape:collect="always"
+ id="filter891"
+ inkscape:label="Badge Shadow">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.71999962"
+ id="feGaussianBlur893" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.0745362"
+ inkscape:cx="119.38505"
+ inkscape:cy="39.201101"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1600"
+ inkscape:window-height="876"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ showborder="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:showpageshadow="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid821" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="16,48"
+ id="guide823" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="64,80"
+ id="guide825" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="80,40"
+ id="guide827" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="64,16"
+ id="guide829" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6522">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="BACKGROUND"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(268,-635.29076)"
+ style="display:inline">
+ <path
+ style="display:inline;fill:#d0ffff;fill-opacity:1;stroke:none;filter:url(#filter1121)"
+ d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 c -27.21517,0 -31.10302,-3.89189 -31.10302,-31.13514 z"
+ id="path6455"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sssssssss" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="PLACE YOUR PICTOGRAM HERE"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <image
+ y="19.840546"
+ x="15"
+ id="image3432"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA2MAAALTCAYAAACbjNrIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB VHic7N1pmFXVlTfw/9rn1sQkoDgBVQXibDSKsyYWQ40IMbGr4myMA4lJzNx5+83b3TdDJ51Od9JJ xyiTRAKFXXS6k6iFUIVFOo5RMhmSmKCC4ogiMlfde/Z6PwCGIEMN55y1zz7r96GfJ0/DOX+suvfs dfbeawNKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkop pZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWU UkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJK KaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSiml lFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRS SimllFJKKaWUUkoppZSzSDqAUiob8vm8qbz48ZOKCE+H5aMM0REgLmGLzUTUYwkvM9t1AJ5/OVd8 KT9pZVE6s1LKIzVdufFDNxxbLBQribgKMMcAtpRghjHbAhu8bti8woZ+u+6cPz6NfN5KR1ZK+U+L MaVUbO5qbxwVlobvB9MHAL4QwNBe/tUigLUMftqweZoNP22JnsRhrz818+xVhRgjK6VSbuLEWSUb jh5yOiwmEtOJIJwE0AkAVwPI9fIyW0B4nBkduVzunmfvbXk+xshKqQzTYkylynfbG8ty1gwqGWwH B2GhlIvB0GIJghION3WT7eYdFduP2D5oc0vLklA6a5bNWlF/lmHOg9EEIIjw0jvA+BUDPwuMufeF h85/PK9vr5XKtua2oGp7eB6HfCkBl4BwFoDyCO9gCbgPxP+4tv3qX0d4XdVXzW1B5fbCMEslgwwX yqgYDDdAGObCLaHlnsDQtlI7YvuapU3d0lGV6i0txpTT2tqaSzeNfKuZQO8j8DnMqARgevFXQyK8 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 7sf8xOzDS8G8r+DxheC+L9/9B2kuUQFQhM1dK89QiB/0YIvkmiP5WXKbqmDTJD8u92Oy8KrkeY/+ 9OQQH2d3RIyVu+yOrssWZGt8nmJcYMHmST4zRptlwTL6RaHWL/dhMz8iWb/c8jF4b6bgvZ9a0fPi W+5HxLsOxRiQsLSKMQAAAIwvbOABAAAAACmgGAMAAACAFFCMAQAAAEAKKMYAAAAAIAUUYwAAAACQ AooxAAAAAEgBxRgAAAAApIBiDAAAAABSQDEGAAAAACmgGAMAAACAFFCMAQAAAEAKKMYAAAAAIAUU YwAAAACQAooxAAAAAEgBxRgAAAAApIBiDAAAAABSQDEGAAAAACmgGAMAAACAFFCMAQAAAEAKKMaA 5I1USRsAAACoYhRjQMLctK3kNuQ9SWQBAABA9aIYAxLWV5v9omR/Ktme4q+2Peb6/PS6muuSTwYA AIBqYmkHACayc1f924IY9QELdpa717pUGxRmS5LcD0fTgMkG3Ao/s5H4RD639rmUIwMAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA APwflgAxUsonrkcAAAAASUVORK5CYII= "
+ preserveAspectRatio="none"
+ height="54.204151"
+ width="65" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="BADGE"
+ style="display:none"
+ sodipodi:insensitive="true">
+ <g
+ style="display:inline"
+ transform="translate(-340.00001,-581)"
+ id="g4394"
+ clip-path="none">
+ <g
+ id="g855">
+ <g
+ inkscape:groupmode="maskhelper"
+ id="g870"
+ clip-path="url(#clipPath873)"
+ style="opacity:0.6;filter:url(#filter891)">
+ <circle
+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
+ id="path844"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ </g>
+ <g
+ id="g862">
+ <circle
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ id="path4398"
+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ <circle
+ transform="matrix(1.25,0,0,1.25,33,-100.45273)"
+ id="path4400"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
+ cx="252"
+ cy="552.36218"
+ r="12" />
+ <path
+ sodipodi:type="star"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ id="path4459"
+ sodipodi:sides="5"
+ sodipodi:cx="666.19574"
+ sodipodi:cy="589.50385"
+ sodipodi:r1="7.2431178"
+ sodipodi:r2="4.3458705"
+ sodipodi:arg1="1.0471976"
+ sodipodi:arg2="1.6755161"
+ inkscape:flatsided="false"
+ inkscape:rounded="0.1"
+ inkscape:randomized="0"
+ d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
--- /dev/null
+includes:
+ - 'layer:basic'
+ - 'interface:mysql'
+ - 'interface:openvim-compute'
+ - 'interface:openvim'
+options:
+ basic:
+ packages:
+ - git
+ - screen
+ - wget
+ - mysql-client
+ - python3-git
+ - python-yaml
+ - python-libvirt
+ - python-bottle
+ - python-mysqldb
+ - python-jsonschema
+ - python-paramiko
+ - python-argcomplete
+ - python-requests
+ - python-novaclient
+ - python-keystoneclient
+ - python-glanceclient
+ - python-neutronclient
--- /dev/null
+name: openvim
+summary: Open Virtual Infrastructure Manager
+maintainers:
+ - Rye Terrell <rye.terrell@canonical.com>
+ - George Kraft <george.kraft@canonical.com>
+description: |
+ Installs and configures the OpenVIM controller from OpenMANO.
+tags:
+ - nfv
+ - telco
+ - osm
+series:
+ - xenial
+extra-bindings:
+ public:
+ internal:
+requires:
+ db:
+ interface: mysql
+ compute:
+ interface: openvim-compute
+provides:
+ openvim-controller:
+ interface: openvim
--- /dev/null
+import os
+import json
+import time
+import subprocess
+from git import Repo as gitrepo
+from shutil import rmtree
+
+from charms.reactive import when, when_not, set_state
+from charmhelpers.core.templating import render
+from charmhelpers.core.hookenv import (
+ status_set,
+ leader_set,
+ leader_get,
+ unit_public_ip,
+)
+from charmhelpers.core.unitdata import kv
+from charmhelpers.core.host import (
+ symlink,
+ mkdir,
+ chownr,
+ service_start,
+)
+from charmhelpers.contrib.unison import (
+ create_private_key,
+ create_public_key,
+ ensure_user,
+)
+
+
+def sh(cmd):
+ return subprocess.check_output(cmd, shell=True)
+
+
+def sh_as_openvim(cmd):
+ return sh('sudo -iu openvim ' + cmd)
+
+
+def create_openvim_user():
+ status_set("maintenance", "Creating OpenVIM user")
+ ensure_user('openvim')
+
+
+def initialize_openvim_database(db):
+ status_set("maintenance", "Initializing OpenVIM database")
+ sh_as_openvim("/opt/openmano/openvim/database_utils/init_vim_db.sh -u %s -p %s -d %s -h %s" % (
+ db.user(),
+ db.password(),
+ db.database(),
+ db.host()
+ ))
+
+
+def generate_ssh_key():
+ status_set("maintenance", "Generating ssh key")
+ user = "openvim"
+ folder = "/home/%s/.ssh" % user
+ mkdir(folder, owner=user, group=user, perms=0o775)
+ private_path = "%s/id_rsa" % folder
+ public_path = "%s.pub" % private_path
+ create_private_key(user, private_path)
+ create_public_key(user, private_path, public_path)
+
+
+def add_openvim_to_path():
+ status_set("maintenance", "Adding OpenVIM to path")
+ symlink(
+ '/opt/openmano/scripts/service-openmano.sh',
+ '/usr/bin/service-openmano')
+ symlink('/opt/openmano/openvim/openvim', '/usr/bin/openvim')
+
+
+def download_openvim():
+ status_set("maintenance", "Downloading OpenVIM")
+ if os.path.isdir("/opt/openmano"):
+ rmtree("/opt/openmano")
+ gitrepo.clone_from('https://github.com/tvansteenburgh/openmano.git', '/opt/openmano')
+ chownr('/opt/openmano', owner='openvim', group='openvim', follow_links=False, chowntopdir=True)
+
+
+def configure_openvim(db):
+ status_set("maintenance", "Configuring OpenVIM")
+ render(
+ source="openvimd.cfg",
+ target="/opt/openmano/openvim/openvimd.cfg",
+ owner="openvim",
+ perms=0o664,
+ context={"db": db}
+ )
+
+
+# TODO: possibly combine all of these create functions?
+def create_tenant():
+ status_set("maintenance", "Creating tenant")
+ render(source="tenant.yaml", target="/tmp/tenant.yaml", owner="openvim", perms=0o664, context={})
+ cmd = 'openvim tenant-create /tmp/tenant.yaml'
+ tenant_uuid = sh_as_openvim(cmd).split()[0]
+ tenant_uuid = str(tenant_uuid, 'utf-8')
+ leader_set({'tenant': tenant_uuid})
+ return tenant_uuid
+
+
+def create_image():
+ status_set("maintenance", "Creating image")
+ render(source="image.yaml", target="/tmp/image.yaml", owner="openvim", perms=0o664, context={})
+ cmd = 'openvim image-create /tmp/image.yaml'
+ image_uuid = sh_as_openvim(cmd).split()[0]
+ image_uuid = str(image_uuid, 'utf-8')
+ return image_uuid
+
+
+def create_flavor():
+ status_set("maintenance", "Creating flavor")
+ render(source="flavor.yaml", target="/tmp/flavor.yaml", owner="openvim", perms=0o664, context={})
+ cmd = 'openvim flavor-create /tmp/flavor.yaml'
+ flavor_uuid = sh_as_openvim(cmd).split()[0]
+ flavor_uuid = str(flavor_uuid, 'utf-8')
+ return flavor_uuid
+
+
+# TODO: especially combine these stupid network functions
+def create_default_network():
+ status_set("maintenance", "Creating default network")
+ render(source="net-default.yaml", target="/tmp/net-default.yaml", owner="openvim", perms=0o664, context={})
+ cmd = 'openvim net-create /tmp/net-default.yaml'
+ net_default_uuid = sh_as_openvim(cmd).split()[0]
+ net_default_uuid = str(net_default_uuid, 'utf-8')
+ return net_default_uuid
+
+
+def create_virbr_network():
+ status_set("maintenance", "Creating virbr0 network")
+ render(source="net-virbr0.yaml", target="/tmp/net-virbr0.yaml", owner="openvim", perms=0o664, context={})
+ cmd = 'openvim net-create /tmp/net-virbr0.yaml'
+ net_virbr0_uuid = sh_as_openvim(cmd).split()[0]
+ net_virbr0_uuid = str(net_virbr0_uuid, 'utf-8')
+ return net_virbr0_uuid
+
+
+def create_vm_yaml(image_uuid, flavor_uuid, net_default_uuid, net_virbr0_uuid):
+ status_set("maintenance", "Creating default VM yaml file")
+ render(
+ source="server.yaml",
+ target="/tmp/server.yaml",
+ owner="openvim",
+ perms=0o664,
+ context={
+ "image_uuid": image_uuid,
+ "flavor_uuid": flavor_uuid,
+ "net_default_uuid": net_default_uuid,
+ "net_virbr0_uuid": net_virbr0_uuid
+ }
+ )
+
+
+def create_sane_defaults():
+ tenant_uuid = create_tenant()
+ add_openvim_tenant_env_var(tenant_uuid)
+ image_uuid = create_image()
+ flavor_uuid = create_flavor()
+ net_default_uuid = create_default_network()
+ net_virbr0_uuid = create_virbr_network()
+ create_vm_yaml(
+ image_uuid=image_uuid,
+ flavor_uuid=flavor_uuid,
+ net_default_uuid=net_default_uuid,
+ net_virbr0_uuid=net_virbr0_uuid
+ )
+
+
+def install_openvim_service():
+ status_set("maintenance", "Installing OpenVIM service")
+ if not os.path.exists('/etc/systemd/system'):
+ os.makedirs('/etc/systemd/system')
+ render(
+ source="openvim.service",
+ target="/etc/systemd/system/openvim.service",
+ owner="root",
+ perms=0o644,
+ context={}
+ )
+
+
+def add_openvim_tenant_env_var(tenant_uuid):
+ status_set("maintenance", "Adding OPENVIM_TENANT environment variable")
+ env_line = 'export OPENVIM_TENANT=%s\n' % tenant_uuid
+ with open('/home/openvim/.profile', 'w+') as f:
+ lines = f.readlines()
+ for line in lines:
+ if env_line == line:
+ return
+ f.seek(0)
+ f.truncate()
+ for line in lines:
+ f.write(line)
+ f.write(env_line)
+
+
+def openvim_running():
+ try:
+ sh_as_openvim('openvim tenant-list')
+ return True
+ except:
+ return False
+
+
+def start_openvim():
+ status_set("maintenance", "Starting OpenVIM")
+ service_start('openvim')
+ t0 = time.time()
+ while not openvim_running():
+ if time.time() - t0 > 60:
+ raise Exception('Failed to start openvim.')
+ time.sleep(0.25)
+
+
+@when_not('db.available')
+def not_ready():
+ status_set('waiting', 'MySQL database required')
+
+
+@when('db.available')
+@when_not('openvim-controller.installed')
+def install_openvim_controller(mysql):
+ create_openvim_user()
+ download_openvim()
+ add_openvim_to_path()
+ configure_openvim(mysql)
+ initialize_openvim_database(mysql)
+ generate_ssh_key()
+ install_openvim_service()
+ start_openvim()
+ create_sane_defaults()
+ status_set(
+ 'active',
+ 'Up on {host}:{port}'.format(
+ host=unit_public_ip(),
+ port='9080'))
+ set_state('openvim-controller.installed')
+
+
+@when('compute.connected', 'openvim-controller.installed')
+def send_ssh_key(compute):
+ with open('/home/openvim/.ssh/id_rsa.pub', 'r') as f:
+ key = f.read().strip()
+ compute.send_ssh_key(key)
+
+
+@when('compute.available', 'openvim-controller.installed')
+def host_add(compute):
+ cache = kv()
+ for node in compute.authorized_nodes():
+ if cache.get("compute:" + node['address']):
+ continue
+ cmd = "ssh -n -o 'StrictHostKeyChecking no' %s@%s"
+ sh_as_openvim(cmd % (node['user'], node['address']))
+ data = {
+ 'host': {
+ 'name': 'compute-0',
+ 'user': node['user'],
+ 'ip_name': node['address'],
+ 'description': 'compute-0'
+ }
+ }
+ with open('/tmp/compute-0.json', 'w') as f:
+ json.dump(data, f, indent=4, sort_keys=True)
+ # TODO: openvim run function!
+ sh_as_openvim('openvim host-add /tmp/compute-0.json')
+ cache.set('compute:' + node['address'], True)
+
+
+@when('openvim-controller.available')
+def openvim_available(openvim):
+ openvim.configure(port=9080, user=leader_get('tenant'))
--- /dev/null
+flavor:
+ name: xenial
+ description: xenial
+ ram: 1024
+ vcpus: 1
--- /dev/null
+image:
+ name: xenial
+ description: xenial
+ path: /opt/VNF/images/ubuntu-16.04-server-cloudimg-amd64-disk1.img
--- /dev/null
+network:
+ name: default
+ type: bridge_man
+ provider:physical: default
+ shared: true
--- /dev/null
+network:
+ name: shared_bridge_net
+ type: bridge_data
+ provider:physical: bridge:virbr0
+ shared: true
--- /dev/null
+[Unit]
+Description=openvim
+
+[Service]
+User=openvim
+ExecStart=/opt/openmano/openvim/openvimd.py -c /opt/openmano/openvim/openvimd.cfg
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+
+#Miscellaneous
+#Option to test openvim without the needed infrastructure, possible values are
+# "normal" by default, Openflow controller (OFC), switch and real host are needed
+# "test" Used for testing http API and database without connecting to host or to OFC
+# "host only" Used when neither OFC nor OF switch are provided.
+# Dataplane network connection must be done manually.
+# "OF only" Used for testing of new openflow controllers support. No real VM deployments will be done but
+# OFC will be used as in real mode
+# "development" Forced a cloud-type deployment, nomal memory instead of hugepages is used,
+# without cpu pinning, and using a bridge network instead of a real OFC dataplane networks.
+# The same 'development_bridge' (see below) is used for all dataplane networks
+mode: host only
+
+#Openflow controller information
+of_controller: floodlight # Type of controller to be used.
+ # Valid controllers are 'opendaylight', 'floodlight' or <custom>
+#of_controller_module: module # Only needed for <custom>. Python module that implement
+ # this controller. By default a file with the name <custom>.py is used
+#of_<other>: value # Other parameters required by <custom> controller. Consumed by __init__
+of_user: user credentials # User credentials for the controller if needed
+of_password: passwd credentials # Password credentials for the controller if needed
+of_controller_ip: 127.0.0.1 # IP address where the Openflow controller is listening
+of_controller_port: 7070 # TCP port where the Openflow controller is listening (REST API server)
+of_controller_dpid: '00:01:02:03:04:05:06:07' # Openflow Switch identifier (put here the right number)
+
+#This option is used for those openflow switch that cannot deliver one packet to several output with different vlan tags
+#When set to true, it fails when trying to attach different vlan tagged ports to the same net
+of_controller_nets_with_same_vlan: false # (by default, true)
+
+#Server parameters
+http_host: 0.0.0.0 # IP address where openvim is listening (by default, localhost)
+http_port: 9080 # General port where openvim is listening (by default, 9080)
+http_admin_port: 9085 # Admin port where openvim is listening (when missing, no administration server is launched)
+
+#database parameters
+db_host: {{ db.host() }}
+db_user: {{ db.user() }}
+db_passwd: {{ db.password() }}
+db_name: {{ db.database() }}
+
+#host paremeters
+image_path: "/opt/VNF/images" # Folder, same for every host, where the VNF images will be copied
+
+#testing parameters (used by ./test/test_openvim.py)
+tenant_id: fc7b43b6-6bfa-11e4-84d2-5254006d6777 # Default tenant identifier for testing
+
+#VLAN ranges used for the dataplane networks (ptp, data)
+#When a network is created an unused value in this range is used
+network_vlan_range_start: 3000
+network_vlan_range_end: 4000
+
+#host bridge interfaces for networks
+# Openvim cannot create bridge networks automatically, in the same way as other CMS do.
+# Bridge networks need to be pre-provisioned on each host and Openvim uses those pre-provisioned bridge networks.
+# Openvim assumes that the following bridge interfaces have been created on each host, appropriately associated to a physical port.
+# The following information needs to be provided:
+# - Name of the bridge (identical in all hosts)
+# - VLAN tag associated to each bridge interface
+# - The speed of the physical port in Gbps, where that bridge interface was created
+# For instance, next example assumes that 10 bridges have been created on each host
+# using vlans 2001 to 2010, associated to a 1Gbps physical port
+bridge_ifaces:
+ #name: [vlan, speed in Gbps]
+ virbrMan1: [2001, 1]
+ virbrMan2: [2002, 1]
+ virbrMan3: [2003, 1]
+ virbrMan4: [2004, 1]
+ virbrMan5: [2005, 1]
+ virbrMan6: [2006, 1]
+ virbrMan7: [2007, 1]
+ virbrMan8: [2008, 1]
+ virbrMan9: [2009, 1]
+ virbrMan10: [2010, 1]
+
+#Used only when 'mode' is at development'. Indicates which 'bridge_ifaces' is used for dataplane networks
+development_bridge: virbrMan10
+
+#DHCP SERVER PARAMETERS.
+#In case some of the previous 'bridge_ifaces' are connected to an EXTERNAL dhcp server, provide
+# the server parameters to allow openvim getting the allocated IP addresses of virtual machines
+# connected to the indicated 'bridge_ifaces' and or 'nets'. Openvim will connect to the dhcp server by ssh.
+#DHCP server must contain a shell script "./get_dhcp_lease.sh" that accept a mac address as parameter
+# and return empty or the allocated IP address. See an example at the end of the file ./openvim/dhcp_thread.py
+#COMMENT all lines in case you do not have a DHCP server in 'normal', 'development' or 'host only' modes.
+# For 'test' or 'OF only' modes you can leave then uncommented, because in these modes fake IP
+# address are generated instead of connecting with a real DHCP server.
+dhcp_server:
+ host: host-ip-or-name
+ #port: 22 #ssh port, by default 22
+ provider: isc-dhcp-server #dhcp-server type
+ user: user
+ #provide password, or key if needed
+ password: passwd
+ #key: ssh-access-key
+ #list of the previous bridge interfaces attached to this dhcp server
+ bridge_ifaces: [ virbrMan1, virbrMan2 ]
+ #list of the networks attached to this dhcp server
+ nets: [default]
+
+
+#logging parameters # DEBUG, INFO, WARNING, ERROR, CRITICAL
+log_level: ERROR
+log_level_db: DEBUG
+log_level_of: DEBUG
--- /dev/null
+server:
+ name: VM
+ description: VM
+ imageRef: {{ image_uuid }}
+ flavorRef: {{ flavor_uuid }}
+ networks:
+ - name: default
+ uuid: {{ net_default_uuid }}
+ - name: virbr0
+ uuid: {{ net_virbr0_uuid }}
--- /dev/null
+tenant:
+ name: openvim-tenant
+ description: openvim-tenant
--- /dev/null
+#!/usr/bin/python3
+
+import amulet
+import openvim
+
+def deploy_openvim():
+ d = amulet.Deployment()
+ d.add("mysql", series="trusty")
+ d.add("openvim-controller", series="xenial")
+ d.add("openvim-compute", charm="local:xenial/openvim-compute", series="xenial")
+ d.relate("openvim-controller:db", "mysql:db")
+ d.relate("openvim-controller:compute", "openvim-compute:compute")
+ d.setup(timeout=900)
+ d.sentry.wait()
+ return d
+
+def get_openvim_connection(deployment):
+ address = deployment.sentry["openvim-controller"][0].info["public-address"]
+ return openvim.connect(address)
+
+def create_vm(deployment):
+ c = get_openvim_connection(deployment)
+ tenant = c.get_tenants()[0]
+ c.set_active_tenant(tenant)
+ networks = c.get_networks()
+ image = c.get_images()[0]
+ flavor = c.get_flavors()[0]
+
+ server = c.create_server(
+ name="vm",
+ description="test vm",
+ image=image,
+ flavor=flavor,
+ networks=networks
+ )
+
+ return server
+
+def test_vm_creation():
+ d = deploy_openvim()
+ create_vm(d)
+
+if __name__ == "__main__":
+ test_vm_creation()
--- /dev/null
+#!/usr/bin/python3
+
+import amulet
+import openvim
+
+deployment = None
+
+def deploy_openvim_without_relations():
+ global deployment
+ deployment = amulet.Deployment()
+ deployment.add("mysql", series="trusty")
+ deployment.add("openvim-controller", series="xenial")
+ deployment.add("openvim-compute", charm="local:xenial/openvim-compute", series="xenial")
+ deployment.expose("openvim-controller")
+ deployment.setup(timeout=900)
+ deployment.sentry.wait()
+
+def add_relations():
+ deployment.relate("openvim-controller:db", "mysql:db")
+ deployment.relate("openvim-controller:compute", "openvim-compute:compute")
+
+def get_openvim_connection():
+ address = deployment.sentry["openvim-controller"][0].info["public-address"]
+ return openvim.connect(address)
+
+def get_first_unit_status(service):
+ service_status = deployment.sentry.get_status()["openvim-controller"]
+ unit_status = next(iter(service_status.values()))
+ return unit_status
+
+def test_controller_blocks_without_mysql():
+ unit_status = get_first_unit_status("openvim-controller")
+ workload_status = unit_status["workload-status"]
+ assert workload_status["current"] == "blocked"
+ assert workload_status["message"] == "mysql database required"
+
+def test_adding_compute_unit():
+ deployment.add_unit("openvim-compute")
+ deployment.sentry.wait(timeout=900)
+ c = get_openvim_connection()
+ assert len(c.get_hosts()) == 2
+
+if __name__ == "__main__":
+ deploy_openvim_without_relations()
+ test_controller_blocks_without_mysql()
+ add_relations()
+ test_adding_compute_unit()
--- /dev/null
+import requests
+import json
+
+class Connection(object):
+ def __init__(self, base_url):
+ self.base_url = base_url
+
+ def set_active_tenant(self, tenant):
+ self.tenant_id = tenant["id"]
+
+ def get_tenants(self):
+ return self._http_get("tenants")["tenants"]
+
+ def get_hosts(self):
+ return self._http_get("hosts")["hosts"]
+
+ def get_networks(self):
+ return self._http_get("networks")["networks"]
+
+ def get_images(self):
+ return self._http_get(self.tenant_id + "/images")["images"]
+
+ def get_flavors(self):
+ return self._http_get(self.tenant_id + "/flavors")["flavors"]
+
+ def create_server(self, name, description, image, flavor, networks):
+ request_data = {"server": {
+ "name": name,
+ "description": description,
+ "imageRef": image["id"],
+ "flavorRef": flavor["id"],
+ "networks": [
+ {"name": n["name"], "uuid": n["id"]}
+ for n in networks
+ ]
+ }}
+
+ path = self.tenant_id + "/servers"
+ return self._http_post(path, request_data)
+
+ def _http_get(self, path):
+ response = requests.get(self.base_url + path)
+ assert response.status_code == 200
+ return response.json()
+
+ def _http_post(self, path, request_data):
+ data = json.dumps(request_data)
+ headers = {"content-type": "application/json"}
+ response = requests.post(self.base_url + path, data=data, headers=headers)
+ assert response.status_code == 200
+ return response.json()
+
+def connect(host, port=9080):
+ base_url = "http://%s:%s/openvim/" % (host, port)
+ return Connection(base_url)
--- /dev/null
+packages:
+ - amulet
+ - python3-requests
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+LICENSE_HEAD='/**
+* Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+* This file is part of openmano
+* All Rights Reserved.
+*
+* 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.
+*
+* For those usages not covered by the Apache License, Version 2.0 please
+* contact with: nfvlabs@tid.es
+**/
+'
+
+
+DBUSER="vim"
+DBPASS=""
+DBHOST="localhost"
+DBPORT="3306"
+DBNAME="vim_db"
+
+# Detect paths
+MYSQL=$(which mysql)
+AWK=$(which awk)
+GREP=$(which grep)
+DIRNAME=`dirname $0`
+
+function usage(){
+ echo -e "Usage: $0 OPTIONS"
+ echo -e " Dumps openvim database content"
+ echo -e " OPTIONS"
+ echo -e " -u USER database user. '$DBUSER' by default. Prompts if DB access fails"
+ echo -e " -p PASS database password. 'No password' by default. Prompts if DB access fails"
+ echo -e " -P PORT database port. '$DBPORT' by default"
+ echo -e " -h HOST database host. '$DBHOST' by default"
+ echo -e " -d NAME database name. '$DBNAME' by default. Prompts if DB access fails"
+ echo -e " --help shows this help"
+}
+
+while getopts ":u:p:P:h:-:" o; do
+ case "${o}" in
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ p)
+ DBPASS="$OPTARG"
+ ;;
+ P)
+ DBPORT="$OPTARG"
+ ;;
+ d)
+ DBNAME="$OPTARG"
+ ;;
+ h)
+ DBHOST="$OPTARG"
+ ;;
+ -)
+ [ "${OPTARG}" == "help" ] && usage && exit 0
+ echo "Invalid option: --$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ :)
+ echo "Option -$OPTARG requires an argument." >&2 && usage >&2
+ exit 1
+ ;;
+ *)
+ usage >&2
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+#check and ask for database user password
+DBUSER_="-u$DBUSER"
+DBPASS_=""
+[ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+DBHOST_="-h$DBHOST"
+DBPORT_="-P$DBPORT"
+while ! echo ";" | mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME >/dev/null 2>&1
+do
+ [ -n "$logintry" ] && echo -e "\nInvalid database credentials!!!. Try again (Ctrl+c to abort)"
+ [ -z "$logintry" ] && echo -e "\nProvide database name and credentials"
+ read -e -p "mysql database name($DBNAME): " KK
+ [ -n "$KK" ] && DBNAME="$KK"
+ read -e -p "mysql user($DBUSER): " KK
+ [ -n "$KK" ] && DBUSER="$KK" && DBUSER_="-u$DBUSER"
+ read -e -s -p "mysql password: " DBPASS
+ [ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+ [ -z "$DBPASS" ] && DBPASS_=""
+ logintry="yes"
+ echo
+done
+
+
+#echo structure, including the content of schema_version
+echo "$LICENSE_HEAD" > ${DIRNAME}/${DBNAME}_structure.sql
+mysqldump $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ --no-data --add-drop-table --add-drop-database --routines --databases $DBNAME >> ${DIRNAME}/${DBNAME}_structure.sql
+echo -e "\n\n\n\n" >> ${DIRNAME}/${DBNAME}_structure.sql
+mysqldump $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ --no-create-info $DBNAME --tables schema_version 2>/dev/null >> ${DIRNAME}/${DBNAME}_structure.sql
+echo " ${DIRNAME}/${DBNAME}_structure.sql"
+
+#echo only data
+echo "$LICENSE_HEAD" > ${DIRNAME}/${DBNAME}_data.sql #copy my own header
+mysqldump $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ --no-create-info $DBNAME >> ${DIRNAME}/${DBNAME}_data.sql
+echo " ${DIRNAME}/${DBNAME}_data.sql"
+
+#echo all
+echo "$LICENSE_HEAD" > ${DIRNAME}/${DBNAME}_all.sql #copy my own header
+mysqldump $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ --add-drop-table --add-drop-database --routines --databases $DBNAME >> ${DIRNAME}/${DBNAME}_all.sql
+echo " ${DIRNAME}/${DBNAME}_all.sql"
+
--- /dev/null
+/*
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+*/
+
+/* This table contains a list of processor ranking
+ The larger ranking the better performance
+ All physical host models must be included in this table
+ before being adding to openvim
+ processor information is obtained with commnand cat /proc/cpuinfo
+ NOTE: Current version of openvim ignores the ranking
+*/
+
+
+LOCK TABLES `host_ranking` WRITE;
+/*!40000 ALTER TABLE `host_ranking` DISABLE KEYS */;
+INSERT INTO `host_ranking`
+ (family, manufacturer, version, description, ranking)
+VALUES
+ ('Xeon','Intel','Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz','sandy bridge',170),
+ ('Xeon','Intel','Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz','sandy bridge',200),
+ ('Xeon','Intel','Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz','ivy bridge',300),
+ ('Xeon','Intel','Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz','ivy bridge',310); /*last entry ends with ';' */
+
+UNLOCK TABLES;
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+DBUSER="vim"
+DBPASS=""
+DBHOST="localhost"
+DBPORT="3306"
+DBNAME="vim_db"
+
+# Detect paths
+MYSQL=$(which mysql)
+AWK=$(which awk)
+GREP=$(which grep)
+DIRNAME=`dirname $0`
+
+function usage(){
+ echo -e "Usage: $0 OPTIONS [{openvim_version}]"
+ echo -e " Inits openvim database; deletes previous one and loads from ${DBNAME}_structure.sql"
+ echo -e " and data from host_ranking.sql, nets.sql, of_ports_pci_correspondece*.sql"
+ echo -e " If openvim_version is not provided it tries to get from openvimd.py using relative path"
+ echo -e " OPTIONS"
+ echo -e " -u USER database user. '$DBUSER' by default. Prompts if DB access fails"
+ echo -e " -p PASS database password. 'No password' by default. Prompts if DB access fails"
+ echo -e " -P PORT database port. '$DBPORT' by default"
+ echo -e " -h HOST database host. '$DBHOST' by default"
+ echo -e " -d NAME database name. '$DBNAME' by default. Prompts if DB access fails"
+ echo -e " --help shows this help"
+}
+
+while getopts ":u:p:P:h:d:-:" o; do
+ case "${o}" in
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ p)
+ DBPASS="$OPTARG"
+ ;;
+ P)
+ DBPORT="$OPTARG"
+ ;;
+ d)
+ DBNAME="$OPTARG"
+ ;;
+ h)
+ DBHOST="$OPTARG"
+ ;;
+ -)
+ [ "${OPTARG}" == "help" ] && usage && exit 0
+ echo "Invalid option: --$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ :)
+ echo "Option -$OPTARG requires an argument." >&2 && usage >&2
+ exit 1
+ ;;
+ *)
+ usage >&2
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+#check and ask for database user password
+DBUSER_="-u$DBUSER"
+DBPASS_=""
+[ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+DBHOST_="-h$DBHOST"
+DBPORT_="-P$DBPORT"
+while ! echo ";" | mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME >/dev/null 2>&1
+do
+ [ -n "$logintry" ] && echo -e "\nInvalid database credentials!!!. Try again (Ctrl+c to abort)"
+ [ -z "$logintry" ] && echo -e "\nProvide database name and credentials"
+ read -e -p "mysql database name($DBNAME): " KK
+ [ -n "$KK" ] && DBNAME="$KK"
+ read -e -p "mysql user($DBUSER): " KK
+ [ -n "$KK" ] && DBUSER="$KK" && DBUSER_="-u$DBUSER"
+ read -e -s -p "mysql password: " DBPASS
+ [ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+ [ -z "$DBPASS" ] && DBPASS_=""
+ logintry="yes":
+ echo
+done
+
+echo " loading ${DIRNAME}/vim_db_structure.sql"
+sed -e "s/vim_db/$DBNAME/" ${DIRNAME}/vim_db_structure.sql | mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_
+
+echo " migrage database version"
+${DIRNAME}/migrate_vim_db.sh $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ -d$DBNAME $1
+
+echo " loading ${DIRNAME}/host_ranking.sql"
+mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME < ${DIRNAME}/host_ranking.sql
+
+echo " loading ${DIRNAME}/of_ports_pci_correspondence.sql"
+mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME < ${DIRNAME}/of_ports_pci_correspondence.sql
+#mysql -h $HOST -P $PORT -u $MUSER -p$MPASS $MDB < ${DIRNAME}/of_ports_pci_correspondence_centos.sql
+
+echo " loading ${DIRNAME}/nets.sql"
+mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME < ${DIRNAME}/nets.sql
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#Upgrade/Downgrade openvim database preserving the content
+#
+
+DBUSER="vim"
+DBPASS=""
+DBHOST="localhost"
+DBPORT="3306"
+DBNAME="vim_db"
+
+# Detect paths
+MYSQL=$(which mysql)
+AWK=$(which awk)
+GREP=$(which grep)
+DIRNAME=`dirname $0`
+
+function usage(){
+ echo -e "Usage: $0 OPTIONS [{openvim_version}]"
+ echo -e " Upgrades/Downgrades openvim database preserving the content"
+ echo -e " if openvim_version is not provided it tries to get from openvimd.py using relative path"
+ echo -e " OPTIONS"
+ echo -e " -u USER database user. '$DBUSER' by default. Prompts if DB access fails"
+ echo -e " -p PASS database password. 'No password' by default. Prompts if DB access fails"
+ echo -e " -P PORT database port. '$DBPORT' by default"
+ echo -e " -h HOST database host. '$DBHOST' by default"
+ echo -e " -d NAME database name. '$DBNAME' by default. Prompts if DB access fails"
+ echo -e " --help shows this help"
+}
+
+while getopts ":u:p:P:h:d:-:" o; do
+ case "${o}" in
+ u)
+ DBUSER="$OPTARG"
+ ;;
+ p)
+ DBPASS="$OPTARG"
+ ;;
+ P)
+ DBPORT="$OPTARG"
+ ;;
+ d)
+ DBNAME="$OPTARG"
+ ;;
+ h)
+ DBHOST="$OPTARG"
+ ;;
+ -)
+ [ "${OPTARG}" == "help" ] && usage && exit 0
+ echo "Invalid option: --$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2 && usage >&2
+ exit 1
+ ;;
+ :)
+ echo "Option -$OPTARG requires an argument." >&2 && usage >&2
+ exit 1
+ ;;
+ *)
+ usage >&2
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+
+#GET OPENVIM VERSION
+OPENVIM_VER="$1"
+if [ -z "$OPENVIM_VER" ]
+then
+ OPENVIM_VER=`${DIRNAME}/../openvimd.py -v`
+ OPENVIM_VER=${OPENVIM_VER%%-r*}
+ OPENVIM_VER=${OPENVIM_VER##*version }
+ echo " Detected openvim version $OPENVIM_VER"
+fi
+VERSION_1=`echo $OPENVIM_VER | cut -f 1 -d"."`
+VERSION_2=`echo $OPENVIM_VER | cut -f 2 -d"."`
+VERSION_3=`echo $OPENVIM_VER | cut -f 3 -d"."`
+if ! [ "$VERSION_1" -ge 0 -a "$VERSION_2" -ge 0 -a "$VERSION_3" -ge 0 ] 2>/dev/null
+then
+ [ -n "$1" ] && echo "Invalid openvim version '$1', expected 'X.X.X'" >&2
+ [ -z "$1" ] && echo "Can not get openvim version" >&2
+ exit -1
+fi
+OPENVIM_VER_NUM=`printf "%d%03d%03d" ${VERSION_1} ${VERSION_2} ${VERSION_3}`
+
+#check and ask for database user password
+DBUSER_="-u$DBUSER"
+[ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+DBHOST_="-h$DBHOST"
+DBPORT_="-P$DBPORT"
+while ! echo ";" | mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME >/dev/null 2>&1
+do
+ [ -n "$logintry" ] && echo -e "\nInvalid database credentials!!!. Try again (Ctrl+c to abort)"
+ [ -z "$logintry" ] && echo -e "\nProvide database name and credentials"
+ read -e -p "mysql database name($DBNAME): " KK
+ [ -n "$KK" ] && DBNAME="$KK"
+ read -e -p "mysql user($DBUSER): " KK
+ [ -n "$KK" ] && DBUSER="$KK" && DBUSER_="-u$DBUSER"
+ read -e -s -p "mysql password: " DBPASS
+ [ -n "$DBPASS" ] && DBPASS_="-p$DBPASS"
+ [ -z "$DBPASS" ] && DBPASS_=""
+ logintry="yes"
+ echo
+done
+
+DBCMD="mysql $DBHOST_ $DBPORT_ $DBUSER_ $DBPASS_ $DBNAME"
+#echo DBCMD $DBCMD
+
+#GET DATABASE VERSION
+#check that the database seems a openvim database
+if ! echo -e "show create table instances;\nshow create table numas" | $DBCMD >/dev/null 2>&1
+then
+ echo " database $DBNAME does not seem to be an openvim database" >&2
+ exit -1;
+fi
+
+if ! echo 'show create table schema_version;' | $DBCMD >/dev/null 2>&1
+then
+ DATABASE_VER="0.0"
+ DATABASE_VER_NUM=0
+else
+ DATABASE_VER_NUM=`echo "select max(version_int) from schema_version;" | $DBCMD | tail -n+2`
+ DATABASE_VER=`echo "select version from schema_version where version_int='$DATABASE_VER_NUM';" | $DBCMD | tail -n+2`
+ [ "$DATABASE_VER_NUM" -lt 0 -o "$DATABASE_VER_NUM" -gt 100 ] && echo " Error can not get database version ($DATABASE_VER?)" >&2 && exit -1
+ #echo "_${DATABASE_VER_NUM}_${DATABASE_VER}"
+fi
+
+
+#GET DATABASE TARGET VERSION
+DATABASE_TARGET_VER_NUM=0
+[ $OPENVIM_VER_NUM -gt 1091 ] && DATABASE_TARGET_VER_NUM=1 #>0.1.91 => 1
+[ $OPENVIM_VER_NUM -ge 2003 ] && DATABASE_TARGET_VER_NUM=2 #0.2.03 => 2
+[ $OPENVIM_VER_NUM -ge 2005 ] && DATABASE_TARGET_VER_NUM=3 #0.2.5 => 3
+[ $OPENVIM_VER_NUM -ge 3001 ] && DATABASE_TARGET_VER_NUM=4 #0.3.1 => 4
+[ $OPENVIM_VER_NUM -ge 4001 ] && DATABASE_TARGET_VER_NUM=5 #0.4.1 => 5
+[ $OPENVIM_VER_NUM -ge 4002 ] && DATABASE_TARGET_VER_NUM=6 #0.4.2 => 6
+[ $OPENVIM_VER_NUM -ge 4005 ] && DATABASE_TARGET_VER_NUM=7 #0.4.5 => 7
+#TODO ... put next versions here
+
+
+function upgrade_to_1(){
+ echo " upgrade database from version 0.0 to version 0.1"
+ echo " CREATE TABLE \`schema_version\`"
+ echo "CREATE TABLE \`schema_version\` (
+ \`version_int\` INT NOT NULL COMMENT 'version as a number. Must not contain gaps',
+ \`version\` VARCHAR(20) NOT NULL COMMENT 'version as a text',
+ \`openvim_ver\` VARCHAR(20) NOT NULL COMMENT 'openvim version',
+ \`comments\` VARCHAR(2000) NULL COMMENT 'changes to database',
+ \`date\` DATE NULL,
+ PRIMARY KEY (\`version_int\`)
+ )
+ COMMENT='database schema control version'
+ COLLATE='utf8_general_ci'
+ ENGINE=InnoDB;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO \`schema_version\` (\`version_int\`, \`version\`, \`openvim_ver\`, \`comments\`, \`date\`)
+ VALUES (1, '0.1', '0.2.00', 'insert schema_version; alter nets with last_error column', '2015-05-05');" | $DBCMD
+ echo " ALTER TABLE \`nets\`, ADD COLUMN \`last_error\`"
+ echo "ALTER TABLE \`nets\`
+ ADD COLUMN \`last_error\` VARCHAR(200) NULL AFTER \`status\`;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_1(){
+ echo " downgrade database from version 0.1 to version 0.0"
+ echo " ALTER TABLE \`nets\` DROP COLUMN \`last_error\`"
+ echo "ALTER TABLE \`nets\` DROP COLUMN \`last_error\`;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " DROP TABLE \`schema_version\`"
+ echo "DROP TABLE \`schema_version\`;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function upgrade_to_2(){
+ echo " upgrade database from version 0.1 to version 0.2"
+ echo " ALTER TABLE \`of_ports_pci_correspondence\` \`resources_port\` \`ports\` ADD COLUMN \`switch_dpid\`"
+ for table in of_ports_pci_correspondence resources_port ports
+ do
+ echo "ALTER TABLE \`${table}\`
+ ADD COLUMN \`switch_dpid\` CHAR(23) NULL DEFAULT NULL AFTER \`switch_port\`; " | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE ${table} CHANGE COLUMN switch_port switch_port VARCHAR(24) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ [ $table == of_ports_pci_correspondence ] ||
+ echo "ALTER TABLE ${table} DROP INDEX vlan_switch_port, ADD UNIQUE INDEX vlan_switch_port (vlan, switch_port, switch_dpid);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ done
+ echo " UPDATE procedure UpdateSwitchPort"
+ echo "DROP PROCEDURE IF EXISTS UpdateSwitchPort;
+ delimiter //
+ CREATE PROCEDURE UpdateSwitchPort() MODIFIES SQL DATA SQL SECURITY INVOKER
+ COMMENT 'Load the openflow switch ports from of_ports_pci_correspondece into resoureces_port and ports'
+ BEGIN
+ #DELETES switch_port entry before writing, because if not it fails for key constrains
+ UPDATE ports
+ RIGHT JOIN resources_port as RP on ports.uuid=RP.port_id
+ INNER JOIN resources_port as RP2 on RP2.id=RP.root_id
+ INNER JOIN numas on RP.numa_id=numas.id
+ INNER JOIN hosts on numas.host_id=hosts.uuid
+ INNER JOIN of_ports_pci_correspondence as PC on hosts.ip_name=PC.ip_name and RP2.pci=PC.pci
+ SET ports.switch_port=null, ports.switch_dpid=null, RP.switch_port=null, RP.switch_dpid=null;
+ #write switch_port into resources_port and ports
+ UPDATE ports
+ RIGHT JOIN resources_port as RP on ports.uuid=RP.port_id
+ INNER JOIN resources_port as RP2 on RP2.id=RP.root_id
+ INNER JOIN numas on RP.numa_id=numas.id
+ INNER JOIN hosts on numas.host_id=hosts.uuid
+ INNER JOIN of_ports_pci_correspondence as PC on hosts.ip_name=PC.ip_name and RP2.pci=PC.pci
+ SET ports.switch_port=PC.switch_port, ports.switch_dpid=PC.switch_dpid, RP.switch_port=PC.switch_port, RP.switch_dpid=PC.switch_dpid;
+ END//
+ delimiter ;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO \`schema_version\` (\`version_int\`, \`version\`, \`openvim_ver\`, \`comments\`, \`date\`)
+ VALUES (2, '0.2', '0.2.03', 'update Procedure UpdateSwitchPort', '2015-05-06');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function upgrade_to_3(){
+ echo " upgrade database from version 0.2 to version 0.3"
+ echo " change size of source_name at table resources_port"
+ echo "ALTER TABLE resources_port CHANGE COLUMN source_name source_name VARCHAR(24) NULL DEFAULT NULL AFTER port_id;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " CREATE PROCEDURE GetAllAvailablePorts"
+ echo "delimiter //
+ CREATE PROCEDURE GetAllAvailablePorts(IN Numa INT) CONTAINS SQL SQL SECURITY INVOKER
+ COMMENT 'Obtain all -including those not connected to switch port- ports available for a numa'
+ BEGIN
+ SELECT port_id, pci, Mbps, Mbps - Mbps_consumed as Mbps_free, totalSRIOV - coalesce(usedSRIOV,0) as availableSRIOV, switch_port, mac
+ FROM
+ (
+ SELECT id as port_id, Mbps, pci, switch_port, mac
+ FROM resources_port
+ WHERE numa_id = Numa AND id=root_id AND status = 'ok' AND instance_id IS NULL
+ ) as A
+ INNER JOIN
+ (
+ SELECT root_id, sum(Mbps_used) as Mbps_consumed, COUNT(id)-1 as totalSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok'
+ GROUP BY root_id
+ ) as B
+ ON A.port_id = B.root_id
+ LEFT JOIN
+ (
+ SELECT root_id, COUNT(id) as usedSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok' AND instance_id IS NOT NULL
+ GROUP BY root_id
+ ) as C
+ ON A.port_id = C.root_id
+ ORDER BY Mbps_free, availableSRIOV, pci;
+ END//
+ delimiter ;"| $DBCMD || ! ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (3, '0.3', '0.2.5', 'New Procedure GetAllAvailablePorts', '2015-07-09');"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+function upgrade_to_4(){
+ echo " upgrade database from version 0.3 to version 0.4"
+ echo " remove unique VLAN index at 'resources_port', 'ports'"
+ echo "ALTER TABLE resources_port DROP INDEX vlan_switch_port;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE ports DROP INDEX vlan_switch_port;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " change table 'ports'"
+ echo "ALTER TABLE ports CHANGE COLUMN model model VARCHAR(12) NULL DEFAULT NULL COMMENT 'driver model for bridge ifaces; PF,VF,VFnotShared for data ifaces' AFTER mac;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE ports DROP COLUMN vlan_changed;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE resources_port DROP COLUMN vlan;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (4, '0.4', '0.3.1', 'Remove unique index VLAN at resources_port', '2015-09-04');"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+function upgrade_to_X(){
+ #TODO, this change of foreign key does not work
+ echo " upgrade database from version 0.X to version 0.X"
+ echo "ALTER TABLE instances DROP FOREIGN KEY FK_instances_flavors, DROP INDEX FK_instances_flavors,
+ DROP FOREIGN KEY FK_instances_images, DROP INDEX FK_instances_flavors,;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE instances
+ ADD CONSTRAINT FK_instances_flavors FOREIGN KEY (flavor_id, tenant_id) REFERENCES tenants_flavors (flavor_id, tenant_id),
+ ADD CONSTRAINT FK_instances_images FOREIGN KEY (image_id, tenant_id) REFERENCES tenants_images (image_id, tenant_id);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+function downgrade_from_2(){
+ echo " downgrade database from version 0.2 to version 0.1"
+ echo " UPDATE procedure UpdateSwitchPort"
+ echo "DROP PROCEDURE IF EXISTS UpdateSwitchPort;
+ delimiter //
+ CREATE PROCEDURE UpdateSwitchPort() MODIFIES SQL DATA SQL SECURITY INVOKER
+ BEGIN
+ UPDATE
+ resources_port INNER JOIN (
+ SELECT resources_port.id,KK.switch_port
+ FROM resources_port INNER JOIN numas on resources_port.numa_id=numas.id
+ INNER JOIN hosts on numas.host_id=hosts.uuid
+ INNER JOIN of_ports_pci_correspondence as KK on hosts.ip_name=KK.ip_name and resources_port.pci=KK.pci
+ ) as TABLA
+ ON resources_port.root_id=TABLA.id
+ SET resources_port.switch_port=TABLA.switch_port
+ WHERE resources_port.root_id=TABLA.id;
+ END//
+ delimiter ;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " ALTER TABLE \`of_ports_pci_correspondence\` \`resources_port\` \`ports\` DROP COLUMN \`switch_dpid\`"
+ for table in of_ports_pci_correspondence resources_port ports
+ do
+ [ $table == of_ports_pci_correspondence ] ||
+ echo "ALTER TABLE ${table} DROP INDEX vlan_switch_port, ADD UNIQUE INDEX vlan_switch_port (vlan, switch_port);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE \`${table}\` DROP COLUMN \`switch_dpid\`;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ switch_port_size=12
+ [ $table == of_ports_pci_correspondence ] && switch_port_size=50
+ echo "ALTER TABLE ${table} CHANGE COLUMN switch_port switch_port VARCHAR(${switch_port_size}) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ done
+ echo "DELETE FROM \`schema_version\` WHERE \`version_int\` = '2';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_3(){
+ echo " downgrade database from version 0.3 to version 0.2"
+ echo " change back size of source_name at table resources_port"
+ echo "ALTER TABLE resources_port CHANGE COLUMN source_name source_name VARCHAR(20) NULL DEFAULT NULL AFTER port_id;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " DROP PROCEDURE GetAllAvailablePorts"
+ echo "DROP PROCEDURE GetAllAvailablePorts;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int = '3';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_4(){
+ echo " downgrade database from version 0.4 to version 0.3"
+ echo " adding back unique index VLAN at 'resources_port','ports'"
+ echo "ALTER TABLE resources_port ADD COLUMN vlan SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER Mbps_used;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "UPDATE resources_port SET vlan= 99+id-root_id WHERE id != root_id;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE resources_port ADD UNIQUE INDEX vlan_switch_port (vlan, switch_port, switch_dpid);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE ports ADD UNIQUE INDEX vlan_switch_port (vlan, switch_port, switch_dpid);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo " change back table 'ports'"
+ echo "ALTER TABLE ports CHANGE COLUMN model model VARCHAR(12) NULL DEFAULT NULL AFTER mac;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE ports ADD COLUMN vlan_changed SMALLINT(5) NULL DEFAULT NULL COMMENT '!=NULL when original vlan have been changed to match a pmp net with all ports in the same vlan' AFTER switch_port;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int = '4';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+
+function upgrade_to_5(){
+ echo " upgrade database from version 0.4 to version 0.5"
+ echo " add 'ip_address' to ports'"
+ echo "ALTER TABLE ports ADD COLUMN ip_address VARCHAR(64) NULL DEFAULT NULL AFTER mac;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (5, '0.5', '0.4.1', 'Add ip_address to ports', '2015-09-04');"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_5(){
+ echo " downgrade database from version 0.5 to version 0.4"
+ echo " removing 'ip_address' from 'ports'"
+ echo "ALTER TABLE ports DROP COLUMN ip_address;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int = '5';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+function upgrade_to_6(){
+ echo " upgrade database from version 0.5 to version 0.6"
+ echo " Change enalarge name, description to 255 at all database"
+ for table in flavors images instances tenants
+ do
+ name_length=255
+ [[ $table == tenants ]] || name_length=64
+ echo -en " $table \r"
+ echo "ALTER TABLE $table CHANGE COLUMN name name VARCHAR($name_length) NOT NULL, CHANGE COLUMN description description VARCHAR(255) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ done
+ echo -en " hosts \r"
+ echo "ALTER TABLE hosts CHANGE COLUMN name name VARCHAR(255) NOT NULL, CHANGE COLUMN ip_name ip_name VARCHAR(64) NOT NULL, CHANGE COLUMN user user VARCHAR(64) NOT NULL, CHANGE COLUMN password password VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN description description VARCHAR(255) NULL DEFAULT NULL, CHANGE COLUMN features features VARCHAR(255) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " nets \r"
+ echo "ALTER TABLE nets CHANGE COLUMN name name VARCHAR(255) NOT NULL, CHANGE COLUMN last_error last_error VARCHAR(255) NULL DEFAULT NULL, CHANGE COLUMN bind bind VARCHAR(36) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " instances \r"
+ echo "ALTER TABLE instances CHANGE COLUMN last_error last_error VARCHAR(255) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " ports \r"
+ echo "ALTER TABLE ports CHANGE COLUMN name name VARCHAR(64) NOT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(64) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " of_flows \r"
+ echo "ALTER TABLE of_flows CHANGE COLUMN name name VARCHAR(64) NOT NULL, CHANGE COLUMN net_id net_id VARCHAR(36) NULL DEFAULT NULL, CHANGE COLUMN actions actions VARCHAR(255) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " of_ports_pci_cor... \r"
+ echo "ALTER TABLE of_ports_pci_correspondence CHANGE COLUMN ip_name ip_name VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(64) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " resources_port \r"
+ echo "ALTER TABLE resources_port CHANGE COLUMN source_name source_name VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(64) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(64) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (6, '0.6', '0.4.2', 'Enlarging name at database', '2016-02-01');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_6(){
+ echo " downgrade database from version 0.6 to version 0.5"
+ echo " Change back name,description to shorter length at all database"
+ for table in flavors images instances tenants
+ do
+ name_length=50
+ [[ $table == flavors ]] || [[ $table == images ]] || name_length=36
+ echo -en " $table \r"
+ echo "ALTER TABLE $table CHANGE COLUMN name name VARCHAR($name_length) NOT NULL, CHANGE COLUMN description description VARCHAR(100) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ done
+ echo -en " hosts \r"
+ echo "ALTER TABLE hosts CHANGE COLUMN name name VARCHAR(36) NOT NULL, CHANGE COLUMN ip_name ip_name VARCHAR(36) NOT NULL, CHANGE COLUMN user user VARCHAR(36) NOT NULL, CHANGE COLUMN password password VARCHAR(36) NULL DEFAULT NULL, CHANGE COLUMN description description VARCHAR(100) NULL DEFAULT NULL, CHANGE COLUMN features features VARCHAR(50) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " nets \r"
+ echo "ALTER TABLE nets CHANGE COLUMN name name VARCHAR(50) NOT NULL, CHANGE COLUMN last_error last_error VARCHAR(200) NULL DEFAULT NULL, CHANGE COLUMN bind bind VARCHAR(36) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " instances \r"
+ echo "ALTER TABLE instances CHANGE COLUMN last_error last_error VARCHAR(200) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " ports \r"
+ echo "ALTER TABLE ports CHANGE COLUMN name name VARCHAR(25) NULL DEFAULT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(24) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(23) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " of_flows \r"
+ echo "ALTER TABLE of_flows CHANGE COLUMN name name VARCHAR(50) NULL DEFAULT NULL, CHANGE COLUMN net_id net_id VARCHAR(50) NULL DEFAULT NULL, CHANGE COLUMN actions actions VARCHAR(100) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " of_ports_pci_cor... \r"
+ echo "ALTER TABLE of_ports_pci_correspondence CHANGE COLUMN ip_name ip_name VARCHAR(50) NULL DEFAULT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(24) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(23) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo -en " resources_port \r"
+ echo "ALTER TABLE resources_port CHANGE COLUMN source_name source_name VARCHAR(24) NULL DEFAULT NULL, CHANGE COLUMN switch_port switch_port VARCHAR(24) NULL DEFAULT NULL, CHANGE COLUMN switch_dpid switch_dpid VARCHAR(23) NULL DEFAULT NULL;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int='6';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function upgrade_to_7(){
+ echo " upgrade database from version 0.6 to version 0.7"
+ echo " add 'bind_net','bind_type','cidr','enable_dhcp' to 'nets'"
+ echo "ALTER TABLE nets ADD COLUMN cidr VARCHAR(64) NULL DEFAULT NULL AFTER bind, ADD COLUMN enable_dhcp ENUM('true','false') NOT NULL DEFAULT 'false' after cidr, ADD COLUMN dhcp_first_ip VARCHAR(64) NULL DEFAULT NULL AFTER enable_dhcp, ADD COLUMN dhcp_last_ip VARCHAR(64) NULL DEFAULT NULL AFTER dhcp_first_ip;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE nets CHANGE COLUMN bind provider VARCHAR(36) NULL DEFAULT NULL AFTER vlan;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE nets ADD COLUMN bind_net VARCHAR(36) NULL DEFAULT NULL COMMENT 'To connect with other net' AFTER provider, ADD COLUMN bind_type VARCHAR(36) NULL DEFAULT NULL COMMENT 'VLAN:<tag> to insert/remove' after bind_net;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (7, '0.7', '0.4.4', 'Add bind_net to net table', '2016-02-12');"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_7(){
+ echo " downgrade database from version 0.7 to version 0.6"
+ echo " removing 'bind_net','bind_type','cidr','enable_dhcp' from 'nets'"
+ echo "ALTER TABLE nets CHANGE COLUMN provider bind NULL DEFAULT NULL AFTER vlan;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE nets DROP COLUMN cidr, DROP COLUMN enable_dhcp, DROP COLUMN bind_net, DROP COLUMN bind_type, DROP COLUMN dhcp_first_ip, DROP COLUMN dhcp_last_ip;"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int = '7';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+#TODO ... put funtions here
+
+
+[ $DATABASE_TARGET_VER_NUM -eq $DATABASE_VER_NUM ] && echo " current database version $DATABASE_VER is ok"
+#UPGRADE DATABASE step by step
+while [ $DATABASE_TARGET_VER_NUM -gt $DATABASE_VER_NUM ]
+do
+ DATABASE_VER_NUM=$((DATABASE_VER_NUM+1))
+ upgrade_to_${DATABASE_VER_NUM}
+ #FILE_="${DIRNAME}/upgrade_to_${DATABASE_VER_NUM}.sh"
+ #[ ! -x "$FILE_" ] && echo "Error, can not find script '$FILE_' to upgrade" >&2 && exit -1
+ #$FILE_ || exit -1 # if fail return
+done
+
+#DOWNGRADE DATABASE step by step
+while [ $DATABASE_TARGET_VER_NUM -lt $DATABASE_VER_NUM ]
+do
+ #FILE_="${DIRNAME}/downgrade_from_${DATABASE_VER_NUM}.sh"
+ #[ ! -x "$FILE_" ] && echo "Error, can not find script '$FILE_' to downgrade" >&2 && exit -1
+ #$FILE_ || exit -1 # if fail return
+ downgrade_from_${DATABASE_VER_NUM}
+ DATABASE_VER_NUM=$((DATABASE_VER_NUM-1))
+done
+
+#echo done
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+/*
+ This table contains a list of networks created from the begining
+ The following fields are needed
+ uuid: provide a valid uuid format
+ type: ptp, data (point to point, or point to multipoint) are openflow dadaplane nets
+ bridge_man, bridge_data are virtio/bridge controlplane nets
+ name: useful human readable name
+ shared: by default true
+ vlan: default vlan of the dataplane net
+ bind: for control plane:
+ default: default network
+ macvtap:host_iface. Connect to a direct macvtap host interface
+ bridge:bridge_name. Connect to this host bridge_name interface
+ for dataplane: NULL, because the binding is done with a external port
+*/
+
+
+LOCK TABLES `nets` WRITE;
+/*
+INSERT INTO `nets`
+ (uuid, `type`, name, shared, vlan, bind)
+VALUES
+ ('00000000-0000-0000-0000-000000000000','bridge_man', 'default', 'true', NULL, 'default'),
+ ('11111111-1111-1111-1111-111111111111','bridge_man', 'direct:em1','true', NULL, 'macvtap:em1'),
+ ('aaaaaaaa-1111-aaaa-aaaa-aaaaaaaaaaaa','data', 'coreIPv4', 'true', 702, NULL),
+ ('aaaaaaaa-aaaa-0000-1111-aaaaaaaaaaaa','bridge_data','virbrMan2', 'true', 2002, 'bridge:virbrMan2') # last row without ','
+;
+*/
+
+UNLOCK TABLES;
+
+/* External PORTS are necessary to connect a dataplane network to an external switch port
+ The following fields are needed
+ uuid: provide a valid uuid format
+ name: useful human readable name
+ net_id: uuid of the net where this port must be connected
+ Mbps: only informative, indicates the expected bandwidth in megabits/s
+ type: only external has meaning here
+ vlan: if the traffic at that port must be vlan tagged
+ switch_port: port name at switch:
+*/
+
+LOCK TABLES `ports` WRITE;
+/*
+INSERT INTO `ports`
+ (uuid, name, net_id, Mbps, type, vlan, switch_port)
+VALUES
+ ('6d536a80-52e9-11e4-9e31-5254006d6777','CoreIPv4', 'aaaaaaaa-1111-aaaa-aaaa-aaaaaaaaaaaa',10000,'external',702, 'Te0/47') # last row without ','
+;
+*/
+
+UNLOCK TABLES;
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+/*
+ READ THIS please:
+ This table contains the matching between dataplane host ports
+ and openflow switch ports.
+ The two first column identifies the host and the pci bus
+ command ethtool -i provides the pci bus port (at host)
+ command ethtool -p makes this port blinking (at host)
+ Last column identifies the switch port name
+ openvim prints at starting the openflow ports naming
+ NOTE: if a host has already been inserted, you must execute
+ UpdateSwitchPort database procedure to associate ports with
+ the switch connection
+*/
+
+LOCK TABLES `of_ports_pci_correspondence` WRITE;
+
+/* DATA for fakehost examples*/
+INSERT INTO `of_ports_pci_correspondence`
+ (ip_name, pci, switch_port)
+VALUES
+ ('fake-host-0', '0000:06:00.0', 'port0/0'),
+ ('fake-host-0', '0000:06:00.1', 'port0/1'),
+ ('fake-host-0', '0000:08:00.0', 'port0/2'),
+ ('fake-host-0', '0000:08:00.1', 'port0/3'),
+
+ ('fake-host-1', '0000:44:00.0', 'port0/4'),
+ ('fake-host-1', '0000:44:00.1', 'port0/5'),
+ ('fake-host-1', '0000:43:00.0', 'port0/6'),
+ ('fake-host-1', '0000:43:00.1', 'port0/7'),
+ ('fake-host-1', '0000:04:00.0', 'port0/8'),
+ ('fake-host-1', '0000:04:00.1', 'port0/9'),
+ ('fake-host-1', '0000:06:00.0', 'port0/10'),
+ ('fake-host-1', '0000:06:00.1', 'port0/11'),
+
+ ('fake-host-2', '0000:44:00.0', 'port0/12'),
+ ('fake-host-2', '0000:44:00.1', 'port0/13'),
+ ('fake-host-2', '0000:43:00.0', 'port0/14'),
+ ('fake-host-2', '0000:43:00.1', 'port0/15'),
+ ('fake-host-2', '0000:04:00.0', 'port0/16'),
+ ('fake-host-2', '0000:04:00.1', 'port0/17'),
+ ('fake-host-2', '0000:06:00.0', 'port0/18'),
+ ('fake-host-2', '0000:06:00.1', 'port0/19'),
+
+ ('fake-host-3', '0000:44:00.0', 'port1/0'),
+ ('fake-host-3', '0000:44:00.1', 'port1/1'),
+ ('fake-host-3', '0000:43:00.0', 'port1/2'),
+ ('fake-host-3', '0000:43:00.1', 'port1/3'),
+ ('fake-host-3', '0000:04:00.0', 'port1/4'),
+ ('fake-host-3', '0000:04:00.1', 'port1/5'),
+ ('fake-host-3', '0000:06:00.0', 'port1/6'),
+ ('fake-host-3', '0000:06:00.1', 'port1/7')
+;
+
+
+UNLOCK TABLES;
--- /dev/null
+/**
+* Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+* This file is part of openmano
+* All Rights Reserved.
+*
+* 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.
+*
+* For those usages not covered by the Apache License, Version 2.0 please
+* contact with: nfvlabs@tid.es
+**/
+
+-- MySQL dump 10.13 Distrib 5.5.43, for debian-linux-gnu (x86_64)
+--
+-- Host: localhost Database: vim_db
+-- ------------------------------------------------------
+-- Server version 5.5.43-0ubuntu0.14.04.1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Current Database: `vim_db`
+--
+
+/*!40000 DROP DATABASE IF EXISTS `vim_db`*/;
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `vim_db` /*!40100 DEFAULT CHARACTER SET utf8 */;
+
+USE `vim_db`;
+
+--
+-- Table structure for table `flavors`
+--
+
+DROP TABLE IF EXISTS `flavors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `flavors` (
+ `uuid` varchar(36) NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `disk` smallint(5) unsigned DEFAULT NULL,
+ `ram` smallint(5) unsigned DEFAULT NULL,
+ `vcpus` smallint(5) unsigned DEFAULT NULL,
+ `extended` varchar(2000) DEFAULT NULL COMMENT 'Extra description yaml format of needed resources and pining, orginized in sets per numa',
+ `public` enum('yes','no') NOT NULL DEFAULT 'no',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='flavors with extra vnfcd info';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `host_ranking`
+--
+
+DROP TABLE IF EXISTS `host_ranking`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `host_ranking` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `family` varchar(50) NOT NULL,
+ `manufacturer` varchar(50) NOT NULL,
+ `version` varchar(50) NOT NULL,
+ `description` varchar(50) DEFAULT NULL,
+ `ranking` smallint(4) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `family_manufacturer_version` (`family`,`manufacturer`,`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `hosts`
+--
+
+DROP TABLE IF EXISTS `hosts`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `hosts` (
+ `uuid` varchar(36) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `ip_name` varchar(64) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `status` enum('ok','error','notused') NOT NULL DEFAULT 'ok',
+ `ranking` smallint(6) NOT NULL DEFAULT '0',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `features` varchar(255) DEFAULT NULL,
+ `user` varchar(64) NOT NULL,
+ `password` varchar(64) DEFAULT NULL,
+ `admin_state_up` enum('true','false') NOT NULL DEFAULT 'true',
+ `RAM` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT 'Host memory in MB not used as hugepages',
+ `cpus` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Host threads(or cores) not isolated from OS',
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `ip_name` (`ip_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='hosts information';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `images`
+--
+
+DROP TABLE IF EXISTS `images`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `images` (
+ `uuid` varchar(36) NOT NULL,
+ `path` varchar(100) NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified_at` timestamp NULL DEFAULT NULL,
+ `public` enum('yes','no') NOT NULL DEFAULT 'no',
+ `progress` tinyint(3) unsigned NOT NULL DEFAULT '100',
+ `status` enum('ACTIVE','DOWN','BUILD','ERROR') NOT NULL DEFAULT 'ACTIVE',
+ `metadata` varchar(2000) DEFAULT NULL COMMENT 'Metatdata in json text format',
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `path` (`path`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `instance_devices`
+--
+
+DROP TABLE IF EXISTS `instance_devices`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `instance_devices` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `type` enum('usb','disk','cdrom','xml') NOT NULL,
+ `xml` varchar(1000) DEFAULT NULL COMMENT 'libvirt XML format for aditional device',
+ `instance_id` varchar(36) NOT NULL,
+ `image_id` varchar(36) DEFAULT NULL COMMENT 'Used in case type is disk',
+ `vpci` char(12) DEFAULT NULL COMMENT 'format XXXX:XX:XX.X',
+ `dev` varchar(12) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `FK_instance_devices_instances` (`instance_id`),
+ KEY `FK_instance_devices_images` (`image_id`),
+ CONSTRAINT `FK_instance_devices_images` FOREIGN KEY (`image_id`) REFERENCES `tenants_images` (`image_id`),
+ CONSTRAINT `FK_instance_devices_instances` FOREIGN KEY (`instance_id`) REFERENCES `instances` (`uuid`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `instances`
+--
+
+DROP TABLE IF EXISTS `instances`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `instances` (
+ `uuid` varchar(36) NOT NULL,
+ `flavor_id` varchar(36) NOT NULL,
+ `image_id` varchar(36) NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `last_error` varchar(255) DEFAULT NULL,
+ `progress` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `tenant_id` varchar(36) NOT NULL,
+ `status` enum('ACTIVE','PAUSED','INACTIVE','CREATING','ERROR','DELETING') NOT NULL DEFAULT 'ACTIVE',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified_at` timestamp NULL DEFAULT NULL,
+ `host_id` varchar(36) NOT NULL COMMENT 'HOST where is allocated',
+ `ram` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT 'used non-hugepages memory in MB',
+ `vcpus` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'used non-isolated CPUs',
+ PRIMARY KEY (`uuid`),
+ KEY `FK_instances_tenants` (`tenant_id`),
+ KEY `FK_instances_flavors` (`flavor_id`),
+ KEY `FK_instances_images` (`image_id`),
+ KEY `FK_instances_hosts` (`host_id`),
+ CONSTRAINT `FK_instances_flavors` FOREIGN KEY (`flavor_id`) REFERENCES `tenants_flavors` (`flavor_id`),
+ CONSTRAINT `FK_instances_hosts` FOREIGN KEY (`host_id`) REFERENCES `hosts` (`uuid`),
+ CONSTRAINT `FK_instances_images` FOREIGN KEY (`image_id`) REFERENCES `tenants_images` (`image_id`),
+ CONSTRAINT `FK_instances_tenants` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='VM instances';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `logs`
+--
+
+DROP TABLE IF EXISTS `logs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `logs` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `tenant_id` varchar(36) DEFAULT NULL,
+ `related` enum('hosts','images','flavors','tenants','ports','instances','nets') DEFAULT NULL,
+ `uuid` varchar(36) DEFAULT NULL COMMENT 'uuid of host, image, etc that log relates to',
+ `level` enum('panic','error','info','debug','verbose') NOT NULL,
+ `description` varchar(200) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `nets`
+--
+
+DROP TABLE IF EXISTS `nets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `nets` (
+ `uuid` varchar(36) NOT NULL,
+ `tenant_id` varchar(36) DEFAULT NULL,
+ `type` enum('ptp','data','bridge_data','bridge_man') NOT NULL DEFAULT 'bridge_man',
+ `status` enum('ACTIVE','DOWN','BUILD','ERROR') NOT NULL DEFAULT 'ACTIVE',
+ `last_error` varchar(255) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `shared` enum('true','false') NOT NULL DEFAULT 'false',
+ `admin_state_up` enum('true','false') NOT NULL DEFAULT 'true',
+ `vlan` smallint(6) DEFAULT NULL,
+ `provider` varchar(36) DEFAULT NULL,
+ `bind_net` varchar(36) DEFAULT NULL COMMENT 'To connect with other net',
+ `bind_type` varchar(36) DEFAULT NULL COMMENT 'VLAN:<tag> to insert/remove',
+ `cidr` varchar(64) DEFAULT NULL,
+ `enable_dhcp` enum('true','false') NOT NULL DEFAULT 'false',
+ `dhcp_first_ip` varchar(64) DEFAULT NULL,
+ `dhcp_last_ip` varchar(64) DEFAULT NULL,
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `type_vlan` (`type`,`vlan`),
+ UNIQUE KEY `physical` (`provider`),
+ KEY `FK_nets_tenants` (`tenant_id`),
+ CONSTRAINT `FK_nets_tenants` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `numas`
+--
+
+DROP TABLE IF EXISTS `numas`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `numas` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `host_id` varchar(36) NOT NULL,
+ `numa_socket` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `hugepages` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Available memory for guest in GB',
+ `status` enum('ok','error','notused') NOT NULL DEFAULT 'ok',
+ `memory` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'total memry in GB, not all available for guests',
+ `admin_state_up` enum('true','false') NOT NULL DEFAULT 'true',
+ PRIMARY KEY (`id`),
+ KEY `FK_numas_hosts` (`host_id`),
+ CONSTRAINT `FK_numas_hosts` FOREIGN KEY (`host_id`) REFERENCES `hosts` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `of_flows`
+--
+
+DROP TABLE IF EXISTS `of_flows`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `of_flows` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(64) NOT NULL,
+ `net_id` varchar(36) DEFAULT NULL,
+ `priority` int(10) unsigned DEFAULT NULL,
+ `vlan_id` smallint(5) unsigned DEFAULT NULL,
+ `ingress_port` varchar(10) DEFAULT NULL,
+ `src_mac` varchar(50) DEFAULT NULL,
+ `dst_mac` varchar(50) DEFAULT NULL,
+ `actions` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `FK_of_flows_nets` (`net_id`),
+ CONSTRAINT `FK_of_flows_nets` FOREIGN KEY (`net_id`) REFERENCES `nets` (`uuid`) ON DELETE SET NULL ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `of_ports_pci_correspondence`
+--
+
+DROP TABLE IF EXISTS `of_ports_pci_correspondence`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `of_ports_pci_correspondence` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `ip_name` varchar(64) DEFAULT NULL,
+ `pci` varchar(50) DEFAULT NULL,
+ `switch_port` varchar(64) DEFAULT NULL,
+ `switch_dpid` varchar(64) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `ports`
+--
+
+DROP TABLE IF EXISTS `ports`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ports` (
+ `uuid` varchar(36) NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `instance_id` varchar(36) DEFAULT NULL,
+ `tenant_id` varchar(36) DEFAULT NULL,
+ `net_id` varchar(36) DEFAULT NULL,
+ `vpci` char(12) DEFAULT NULL,
+ `Mbps` mediumint(8) unsigned DEFAULT NULL COMMENT 'In Mbits/s',
+ `admin_state_up` enum('true','false') NOT NULL DEFAULT 'true',
+ `status` enum('ACTIVE','DOWN','BUILD','ERROR') NOT NULL DEFAULT 'ACTIVE',
+ `type` enum('instance:bridge','instance:data','external') NOT NULL DEFAULT 'instance:bridge',
+ `vlan` smallint(5) DEFAULT NULL COMMENT 'vlan of this SRIOV, or external port',
+ `switch_port` varchar(64) DEFAULT NULL,
+ `switch_dpid` varchar(64) DEFAULT NULL,
+ `mac` char(18) DEFAULT NULL COMMENT 'mac address format XX:XX:XX:XX:XX:XX',
+ `ip_address` varchar(64) DEFAULT NULL,
+ `model` varchar(12) DEFAULT NULL COMMENT 'driver model for bridge ifaces; PF,VF,VFnotShared for data ifaces',
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `mac` (`mac`),
+ KEY `FK_instance_ifaces_instances` (`instance_id`),
+ KEY `FK_instance_ifaces_nets` (`net_id`),
+ KEY `FK_ports_tenants` (`tenant_id`),
+ CONSTRAINT `FK_instance_ifaces_nets` FOREIGN KEY (`net_id`) REFERENCES `nets` (`uuid`),
+ CONSTRAINT `FK_ports_instances` FOREIGN KEY (`instance_id`) REFERENCES `instances` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `FK_ports_tenants` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Bridge interfaces used by instances';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `resources_core`
+--
+
+DROP TABLE IF EXISTS `resources_core`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `resources_core` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `numa_id` int(11) DEFAULT NULL,
+ `core_id` smallint(5) unsigned NOT NULL,
+ `thread_id` smallint(5) unsigned NOT NULL,
+ `instance_id` varchar(36) DEFAULT NULL COMMENT 'instance that consume this resource',
+ `v_thread_id` smallint(6) DEFAULT NULL COMMENT 'name used by virtual machine; -1 if this thread is not used because core is asigned completely',
+ `status` enum('ok','error','notused','noteligible') NOT NULL DEFAULT 'ok' COMMENT '''error'': resource not available becasue an error at deployment; ''notused'': admin marked as not available, ''noteligible'': used by host and not available for guests',
+ `paired` enum('Y','N') NOT NULL DEFAULT 'N',
+ PRIMARY KEY (`id`),
+ KEY `FK_resources_core_instances` (`instance_id`),
+ KEY `FK_resources_core_numas` (`numa_id`),
+ CONSTRAINT `FK_resources_core_instances` FOREIGN KEY (`instance_id`) REFERENCES `instances` (`uuid`),
+ CONSTRAINT `FK_resources_core_numas` FOREIGN KEY (`numa_id`) REFERENCES `numas` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Contain an entry by thread (two entries per core) of all available cores. Threy will be free if instance_id is NULL';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `resources_mem`
+--
+
+DROP TABLE IF EXISTS `resources_mem`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `resources_mem` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `numa_id` int(11) NOT NULL DEFAULT '0',
+ `instance_id` varchar(36) DEFAULT '0' COMMENT 'NULL is allowed in order to allow some memory not used',
+ `consumed` int(3) unsigned NOT NULL DEFAULT '0' COMMENT 'In GB',
+ PRIMARY KEY (`id`),
+ KEY `FK_resources_mem_instances` (`instance_id`),
+ KEY `FK_resources_mem_numas` (`numa_id`),
+ CONSTRAINT `FK_resources_mem_instances` FOREIGN KEY (`instance_id`) REFERENCES `instances` (`uuid`) ON DELETE CASCADE,
+ CONSTRAINT `FK_resources_mem_numas` FOREIGN KEY (`numa_id`) REFERENCES `numas` (`id`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Include the hugepages memory used by one instance (VM) in one host NUMA.';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `resources_port`
+--
+
+DROP TABLE IF EXISTS `resources_port`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `resources_port` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `numa_id` int(11) NOT NULL DEFAULT '0',
+ `instance_id` varchar(36) DEFAULT NULL COMMENT 'Contain instance that use this resource completely. NULL if this resource is free or partially used (resources_port_SRIOV)',
+ `port_id` varchar(36) DEFAULT NULL COMMENT 'When resource is used, this point to the ports table',
+ `source_name` varchar(64) DEFAULT NULL,
+ `pci` char(12) NOT NULL DEFAULT '0' COMMENT 'Host physical pci bus. Format XXXX:XX:XX.X',
+ `Mbps` smallint(5) unsigned DEFAULT '10' COMMENT 'Nominal Port speed ',
+ `root_id` int(11) DEFAULT NULL COMMENT 'NULL for physical port entries; =id for SRIOV port',
+ `status` enum('ok','error','notused') NOT NULL DEFAULT 'ok',
+ `Mbps_used` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Speed bandwidth used when asigned',
+ `switch_port` varchar(64) DEFAULT NULL,
+ `switch_dpid` varchar(64) DEFAULT NULL,
+ `mac` char(18) DEFAULT NULL COMMENT 'mac address format XX:XX:XX:XX:XX:XX',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `mac` (`mac`),
+ UNIQUE KEY `port_id` (`port_id`),
+ KEY `FK_resources_port_numas` (`numa_id`),
+ KEY `FK_resources_port_instances` (`instance_id`),
+ CONSTRAINT `FK_resources_port_instances` FOREIGN KEY (`instance_id`) REFERENCES `instances` (`uuid`),
+ CONSTRAINT `FK_resources_port_numas` FOREIGN KEY (`numa_id`) REFERENCES `numas` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `FK_resources_port_ports` FOREIGN KEY (`port_id`) REFERENCES `ports` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Contain NIC ports SRIOV and availabes, and current use. Every port contain several entries, one per port (root_id=NULL) and all posible SRIOV (root_id=id of port)';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `schema_version`
+--
+
+DROP TABLE IF EXISTS `schema_version`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_version` (
+ `version_int` int(11) NOT NULL COMMENT 'version as a number. Must not contain gaps',
+ `version` varchar(20) NOT NULL COMMENT 'version as a text',
+ `openvim_ver` varchar(20) NOT NULL COMMENT 'openvim version',
+ `comments` varchar(2000) DEFAULT NULL COMMENT 'changes to database',
+ `date` date DEFAULT NULL,
+ PRIMARY KEY (`version_int`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='database schema control version';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `tenants`
+--
+
+DROP TABLE IF EXISTS `tenants`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants` (
+ `uuid` varchar(36) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `enabled` enum('true','false') NOT NULL DEFAULT 'true',
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='tenants information';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `tenants_flavors`
+--
+
+DROP TABLE IF EXISTS `tenants_flavors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants_flavors` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `flavor_id` varchar(36) NOT NULL,
+ `tenant_id` varchar(36) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `FK__tenants` (`tenant_id`),
+ KEY `FK__flavors` (`flavor_id`),
+ CONSTRAINT `FK__flavors` FOREIGN KEY (`flavor_id`) REFERENCES `flavors` (`uuid`),
+ CONSTRAINT `FK__tenants` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `tenants_images`
+--
+
+DROP TABLE IF EXISTS `tenants_images`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tenants_images` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `image_id` varchar(36) NOT NULL,
+ `tenant_id` varchar(36) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `FK_tenants_images_tenants` (`tenant_id`),
+ KEY `FK_tenants_images_images` (`image_id`),
+ CONSTRAINT `FK_tenants_images_images` FOREIGN KEY (`image_id`) REFERENCES `images` (`uuid`),
+ CONSTRAINT `FK_tenants_images_tenants` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`uuid`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `uuids`
+--
+
+DROP TABLE IF EXISTS `uuids`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `uuids` (
+ `uuid` varchar(36) NOT NULL,
+ `root_uuid` varchar(36) DEFAULT NULL COMMENT 'Some related UUIDs can be grouped by this field, so that they can be deleted at once',
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `used_at` enum('flavors','hosts','images','instances','nets','ports','tenants') DEFAULT NULL COMMENT 'Table that uses this UUID',
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Used to avoid UUID repetitions';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping routines for database 'vim_db'
+--
+/*!50003 DROP PROCEDURE IF EXISTS `GetAllAvailablePorts` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetAllAvailablePorts`(IN Numa INT)
+ SQL SECURITY INVOKER
+ COMMENT 'Obtain all -including those not connected to switch port- ports available for a numa'
+BEGIN
+ SELECT port_id, pci, Mbps, Mbps - Mbps_consumed as Mbps_free, totalSRIOV - coalesce(usedSRIOV,0) as availableSRIOV, switch_port, mac
+ FROM
+ (
+ SELECT id as port_id, Mbps, pci, switch_port, mac
+ FROM resources_port
+ WHERE numa_id = Numa AND id=root_id AND status = 'ok' AND instance_id IS NULL
+ ) as A
+ INNER JOIN
+ (
+ SELECT root_id, sum(Mbps_used) as Mbps_consumed, COUNT(id)-1 as totalSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok'
+ GROUP BY root_id
+ ) as B
+ ON A.port_id = B.root_id
+ LEFT JOIN
+ (
+ SELECT root_id, COUNT(id) as usedSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok' AND instance_id IS NOT NULL
+ GROUP BY root_id
+ ) as C
+ ON A.port_id = C.root_id
+ ORDER BY Mbps_free, availableSRIOV, pci;
+ END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetAvailablePorts` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetAvailablePorts`(IN `Numa` INT)
+ DETERMINISTIC
+ SQL SECURITY INVOKER
+BEGIN
+SELECT port_id, pci, Mbps, Mbps - Mbps_consumed as Mbps_free, totalSRIOV - coalesce(usedSRIOV,0) as availableSRIOV, switch_port, mac
+FROM
+ (
+ SELECT id as port_id, Mbps, pci, switch_port, mac
+ FROM resources_port
+ WHERE numa_id = Numa AND id=root_id AND status = 'ok' AND switch_port is not Null AND instance_id IS NULL
+ ) as A
+ INNER JOIN
+ (
+ SELECT root_id, sum(Mbps_used) as Mbps_consumed, COUNT(id)-1 as totalSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok'
+ GROUP BY root_id
+ ) as B
+ ON A.port_id = B.root_id
+ LEFT JOIN
+ (
+ SELECT root_id, COUNT(id) as usedSRIOV
+ FROM resources_port
+ WHERE numa_id = Numa AND status = 'ok' AND instance_id IS NOT NULL AND switch_port is not Null
+ GROUP BY root_id
+ ) as C
+ ON A.port_id = C.root_id
+
+ORDER BY Mbps_free, availableSRIOV, pci
+;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetHostByMemCpu` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetHostByMemCpu`(IN `Needed_mem` INT, IN `Needed_cpus` INT)
+ SQL SECURITY INVOKER
+ COMMENT 'Obtain those hosts with the available free Memory(Non HugePages) and CPUS (Non isolated)'
+BEGIN
+
+SELECT *
+FROM hosts as H
+LEFT JOIN (
+ SELECT sum(ram) as used_ram, sum(vcpus) as used_cpus, host_id
+ FROM instances
+ GROUP BY host_id
+) as U ON U.host_id = H.uuid
+WHERE Needed_mem<=H.RAM-coalesce(U.used_ram,0) AND Needed_cpus<=H.cpus-coalesce(U.used_cpus,0) AND H.admin_state_up = 'true'
+ORDER BY RAM-coalesce(U.used_ram,0), cpus-coalesce(U.used_cpus,0)
+
+;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetIfaces` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetIfaces`()
+ SQL SECURITY INVOKER
+ COMMENT 'Used for the http get ports'
+BEGIN
+
+SELECT *, 'ACTIVE' as status,'true' as admin_state_up FROM
+(
+ (
+ SELECT ifa.uuid as id, ifa.name as name, instance_id as device_id, net_id, tenant_id
+ FROM instance_ifaces AS ifa JOIN instances AS i on ifa.instance_id=i.uuid
+ )
+ UNION
+ (
+ SELECT iface_uuid as id, ifa.name as name, instance_id as device_id, net_id,tenant_id
+ FROM resources_port AS ifa JOIN instances AS i on ifa.instance_id=i.uuid
+ WHERE iface_uuid is not NULL
+ )
+ UNION
+ (
+ SELECT uuid as id, name, Null as device_id, net_id, Null as tenant_id
+ FROM external_ports
+ )
+) as B
+;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetNextAutoIncrement` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetNextAutoIncrement`()
+ SQL SECURITY INVOKER
+BEGIN
+SELECT table_name, AUTO_INCREMENT
+FROM information_schema.tables
+WHERE table_name = 'resources_port'
+AND table_schema = DATABASE( ) ;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetNumaByCore` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetNumaByCore`(IN `Needed_cores` SMALLINT)
+ SQL SECURITY INVOKER
+ COMMENT 'Obtain Numas with a concrete number of available cores, with bot'
+BEGIN
+
+SELECT numa_id, host_id, numa_socket, freecores FROM
+(
+ SELECT numa_id, COUNT(core_id) as freecores FROM
+ (
+ SELECT numa_id, core_id, COUNT(thread_id) AS freethreads
+ FROM resources_core
+ WHERE instance_id IS NULL AND status = 'ok'
+ GROUP BY numa_id, core_id
+ ) AS FREECORES_TABLE
+ WHERE FREECORES_TABLE.freethreads = 2
+ GROUP BY numa_id
+) AS NBCORES_TABLE
+INNER JOIN numas ON numas.id = NBCORES_TABLE.numa_id
+INNER JOIN hosts ON numas.host_id = hosts.uuid
+
+WHERE NBCORES_TABLE.freecores >= Needed_cores AND numas.status = 'ok' AND numas.admin_state_up = 'true' AND hosts.admin_state_up = 'true'
+ORDER BY NBCORES_TABLE.freecores
+;
+
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetNumaByMemory` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetNumaByMemory`(IN `Needed_mem` SMALLINT)
+ DETERMINISTIC
+ SQL SECURITY INVOKER
+ COMMENT 'Obtain numas with a free quantity of memory, passed by parameter'
+BEGIN
+SELECT * FROM
+( SELECT numas.id as numa_id, numas.host_id, numas.numa_socket, numas.hugepages, numas.hugepages - sum(coalesce(resources_mem.consumed,0)) AS freemem
+ FROM numas
+ LEFT JOIN resources_mem ON numas.id = resources_mem.numa_id
+ JOIN hosts ON numas.host_id = hosts.uuid
+ WHERE numas.status = 'ok' AND numas.admin_state_up = 'true' AND hosts.admin_state_up = 'true'
+ GROUP BY numas.id
+) AS COMBINED
+
+WHERE COMBINED.freemem >= Needed_mem
+ORDER BY COMBINED.freemem
+;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetNumaByPort` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetNumaByPort`(IN `Needed_speed` SMALLINT, IN `Needed_ports` SMALLINT)
+ SQL SECURITY INVOKER
+ COMMENT 'Busca Numas con N puertos fisicos LIBRES de X velocidad'
+BEGIN
+
+SELECT numa_id, COUNT(id) AS number_ports
+FROM
+(
+ SELECT root_id AS id, status, numa_id, Mbps, SUM(Mbps_used) AS Consumed
+ FROM resources_port
+ GROUP BY root_id
+) AS P
+WHERE status = 'ok' AND switch_port is not Null AND Consumed = 0 AND Mbps >= Needed_speed
+GROUP BY numa_id
+HAVING number_ports >= Needed_ports
+;
+
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetNumaByThread` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetNumaByThread`(IN `Needed_threads` SMALLINT)
+ SQL SECURITY INVOKER
+BEGIN
+
+SELECT numa_id, host_id, numa_socket, freethreads
+FROM
+(
+ SELECT numa_id, COUNT(thread_id) AS freethreads
+ FROM resources_core
+ WHERE instance_id IS NULL AND status = 'ok'
+ GROUP BY numa_id
+) AS NBCORES_TABLE
+INNER JOIN numas ON numas.id = NBCORES_TABLE.numa_id
+INNER JOIN hosts ON numas.host_id = hosts.uuid
+
+WHERE NBCORES_TABLE.freethreads >= Needed_threads AND numas.status = 'ok' AND numas.admin_state_up = 'true' AND hosts.admin_state_up = 'true'
+ORDER BY NBCORES_TABLE.freethreads
+;
+
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `GetPortsFromNuma` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `GetPortsFromNuma`(IN `Numa` INT)
+ NO SQL
+ SQL SECURITY INVOKER
+BEGIN
+SELECT Mbps, pci, status, Mbps_consumed
+FROM
+(
+ SELECT id, Mbps, pci, status
+ FROM resources_port
+ WHERE numa_id = Numa AND id=root_id AND status='ok' AND switch_port is not Null
+) as A
+INNER JOIN
+(
+ SELECT root_id, sum(Mbps_used) as Mbps_consumed
+ FROM resources_port
+ WHERE numa_id = Numa
+ GROUP BY root_id
+) as B
+ON A.id = B.root_id
+;
+END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!50003 DROP PROCEDURE IF EXISTS `UpdateSwitchPort` */;
+/*!50003 SET @saved_cs_client = @@character_set_client */ ;
+/*!50003 SET @saved_cs_results = @@character_set_results */ ;
+/*!50003 SET @saved_col_connection = @@collation_connection */ ;
+/*!50003 SET character_set_client = utf8 */ ;
+/*!50003 SET character_set_results = utf8 */ ;
+/*!50003 SET collation_connection = utf8_general_ci */ ;
+/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
+/*!50003 SET sql_mode = '' */ ;
+DELIMITER ;;
+CREATE PROCEDURE `UpdateSwitchPort`()
+ MODIFIES SQL DATA
+ SQL SECURITY INVOKER
+ COMMENT 'Load the openflow switch ports from of_ports_pci_correspondece into resoureces_port and ports'
+BEGIN
+
+ UPDATE ports
+ RIGHT JOIN resources_port as RP on ports.uuid=RP.port_id
+ INNER JOIN resources_port as RP2 on RP2.id=RP.root_id
+ INNER JOIN numas on RP.numa_id=numas.id
+ INNER JOIN hosts on numas.host_id=hosts.uuid
+ INNER JOIN of_ports_pci_correspondence as PC on hosts.ip_name=PC.ip_name and RP2.pci=PC.pci
+ SET ports.switch_port=null, ports.switch_dpid=null, RP.switch_port=null, RP.switch_dpid=null;
+
+ UPDATE ports
+ RIGHT JOIN resources_port as RP on ports.uuid=RP.port_id
+ INNER JOIN resources_port as RP2 on RP2.id=RP.root_id
+ INNER JOIN numas on RP.numa_id=numas.id
+ INNER JOIN hosts on numas.host_id=hosts.uuid
+ INNER JOIN of_ports_pci_correspondence as PC on hosts.ip_name=PC.ip_name and RP2.pci=PC.pci
+ SET ports.switch_port=PC.switch_port, ports.switch_dpid=PC.switch_dpid, RP.switch_port=PC.switch_port, RP.switch_dpid=PC.switch_dpid;
+ END ;;
+DELIMITER ;
+/*!50003 SET sql_mode = @saved_sql_mode */ ;
+/*!50003 SET character_set_client = @saved_cs_client */ ;
+/*!50003 SET character_set_results = @saved_cs_results */ ;
+/*!50003 SET collation_connection = @saved_col_connection */ ;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2016-05-13 12:52:19
+
+
+
+
+
+-- MySQL dump 10.13 Distrib 5.5.43, for debian-linux-gnu (x86_64)
+--
+-- Host: localhost Database: vim_db
+-- ------------------------------------------------------
+-- Server version 5.5.43-0ubuntu0.14.04.1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Dumping data for table `schema_version`
+--
+
+LOCK TABLES `schema_version` WRITE;
+/*!40000 ALTER TABLE `schema_version` DISABLE KEYS */;
+INSERT INTO `schema_version` VALUES (1,'0.1','0.2.00','insert schema_version; alter nets with last_error column','2015-05-05'),(2,'0.2','0.2.03','update Procedure UpdateSwitchPort','2015-05-06'),(3,'0.3','0.2.5','New Procedure GetAllAvailablePorts','2015-07-09'),(4,'0.4','0.3.1','Remove unique index VLAN at resources_port','2015-09-04'),(5,'0.5','0.4.1','Add ip_address to ports','2015-09-04'),(6,'0.6','0.4.2','Enlarging name at database','2016-02-01'),(7,'0.7','0.4.4','Add bind_net to net table','2016-02-12');
+/*!40000 ALTER TABLE `schema_version` ENABLE KEYS */;
+UNLOCK TABLES;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2016-05-13 12:52:19
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is thread that interact with the dhcp server to get the IP addresses
+'''
+__author__="Pablo Montes, Alfonso Tierno"
+__date__ ="$4-Jan-2016 12:07:15$"
+
+
+
+import threading
+import time
+import Queue
+import paramiko
+import random
+import subprocess
+
+#TODO: insert a logging system
+
+class dhcp_thread(threading.Thread):
+ def __init__(self, dhcp_params, db, db_lock, test, dhcp_nets, debug=None):
+ '''Init a thread.
+ Arguments: thread_info must be a dictionary with:
+ 'dhcp_params' dhcp server parameters with the following keys:
+ mandatory : user, host, port, key, ifaces(interface name list of the one managed by the dhcp)
+ optional: password, key, port(22)
+ 'db' 'db_lock': database class and lock for accessing it
+ 'test': in test mode no acces to a server is done, and ip is invented
+ '''
+ threading.Thread.__init__(self)
+ self.name = "dhcp_thread"
+ self.dhcp_params = dhcp_params
+ self.debug = debug
+ self.db = db
+ self.db_lock = db_lock
+ self.test = test
+ self.dhcp_nets = dhcp_nets
+ self.ssh_conn = None
+
+ self.mac_status ={} #dictionary of mac_address to retrieve information
+ #ip: None
+ #retries:
+ #next_reading: time for the next trying to check ACTIVE status or IP
+ #created: time when it was added
+ #active: time when the VM becomes into ACTIVE status
+
+
+ self.queueLock = threading.Lock()
+ self.taskQueue = Queue.Queue(2000)
+
+ def ssh_connect(self):
+ try:
+ #Connect SSH
+ self.ssh_conn = paramiko.SSHClient()
+ self.ssh_conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.ssh_conn.load_system_host_keys()
+ self.ssh_conn.connect(self.dhcp_params["host"], port=self.dhcp_params.get("port",22),
+ username=self.dhcp_params["user"], password=self.dhcp_params.get("password"), pkey=self.dhcp_params.get("key"),
+ timeout=2)
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": ssh_connect ssh Exception:", text
+
+ def load_mac_from_db(self):
+ #TODO get macs to follow from the database
+ print self.name, " load macs from db"
+ self.db_lock.acquire()
+ r,c = self.db.get_table(SELECT=('mac','ip_address','nets.uuid as net_id', ),
+ FROM='ports join nets on ports.net_id=nets.uuid',
+ WHERE_NOT={'ports.instance_id': None, 'nets.provider': None})
+ self.db_lock.release()
+ now = time.time()
+ self.mac_status ={}
+ if r<0:
+ print self.name, ": Error getting data from database:", c
+ return
+ for port in c:
+ if port["net_id"] in self.dhcp_nets:
+ self.mac_status[ port["mac"] ] = {"ip": port["ip_address"], "next_reading": now, "created": now, "retries":0}
+
+ def insert_task(self, task, *aditional):
+ try:
+ self.queueLock.acquire()
+ task = self.taskQueue.put( (task,) + aditional, timeout=5)
+ self.queueLock.release()
+ return 1, None
+ except Queue.Full:
+ return -1, "timeout inserting a task over host " + self.name
+
+ def run(self):
+ print self.name, " starting, nets", self.dhcp_nets
+ next_iteration = time.time() + 10
+ while True:
+ self.load_mac_from_db()
+ while True:
+ self.queueLock.acquire()
+ if not self.taskQueue.empty():
+ task = self.taskQueue.get()
+ else:
+ task = None
+ self.queueLock.release()
+
+ if task is None:
+ now=time.time()
+ if now >= next_iteration:
+ next_iteration = self.get_ip_from_dhcp()
+ else:
+ time.sleep(1)
+ continue
+
+ if task[0] == 'add':
+ print self.name, ": processing task add mac", task[1]
+ now=time.time()
+ self.mac_status[task[1] ] = {"ip": None, "next_reading": now, "created": now, "retries":0}
+ next_iteration = now
+ elif task[0] == 'del':
+ print self.name, ": processing task del mac", task[1]
+ if task[1] in self.mac_status:
+ del self.mac_status[task[1] ]
+ elif task[0] == 'exit':
+ print self.name, ": processing task exit"
+ self.terminate()
+ return 0
+ else:
+ print self.name, ": unknown task", task
+
+ def terminate(self):
+ try:
+ if self.ssh_conn:
+ self.ssh_conn.close()
+ except Exception as e:
+ text = str(e)
+ print self.name, ": terminate Exception:", text
+ print self.name, ": exit from host_thread"
+
+ def get_ip_from_dhcp(self):
+
+ now = time.time()
+ next_iteration= now + 40000 # >10 hores
+
+ #print self.name, "Iteration"
+ for mac_address in self.mac_status:
+ if now < self.mac_status[mac_address]["next_reading"]:
+ if self.mac_status[mac_address]["next_reading"] < next_iteration:
+ next_iteration = self.mac_status[mac_address]["next_reading"]
+ continue
+
+ if self.mac_status[mac_address].get("active") == None:
+ #check from db if already active
+ self.db_lock.acquire()
+ r,c = self.db.get_table(FROM="ports as p join instances as i on p.instance_id=i.uuid",
+ WHERE={"p.mac": mac_address, "i.status": "ACTIVE"})
+ self.db_lock.release()
+ if r>0:
+ self.mac_status[mac_address]["active"] = now
+ self.mac_status[mac_address]["next_reading"] = (int(now)/2 +1)* 2
+ print self.name, "mac %s VM ACTIVE" % (mac_address)
+ self.mac_status[mac_address]["retries"] = 0
+ else:
+ #print self.name, "mac %s VM INACTIVE" % (mac_address)
+ if now - self.mac_status[mac_address]["created"] > 300:
+ #modify Database to tell openmano that we can not get dhcp from the machine
+ if not self.mac_status[mac_address].get("ip"):
+ self.db_lock.acquire()
+ r,c = self.db.update_rows("ports", {"ip_address": "0.0.0.0"}, {"mac": mac_address})
+ self.db_lock.release()
+ self.mac_status[mac_address]["ip"] = "0.0.0.0"
+ print self.name, "mac %s >> set to 0.0.0.0 because of timeout" % (mac_address)
+ self.mac_status[mac_address]["next_reading"] = (int(now)/60 +1)* 60
+ else:
+ self.mac_status[mac_address]["next_reading"] = (int(now)/6 +1)* 6
+ if self.mac_status[mac_address]["next_reading"] < next_iteration:
+ next_iteration = self.mac_status[mac_address]["next_reading"]
+ continue
+
+
+ if self.test:
+ if self.mac_status[mac_address]["retries"]>random.randint(10,100): #wait between 10 and 100 seconds to produce a fake IP
+ content = self.get_fake_ip()
+ else:
+ content = None
+ elif self.dhcp_params["host"]=="localhost":
+ try:
+ command = ['get_dhcp_lease.sh', mac_address]
+ content = subprocess.check_output(command)
+ except Exception as e:
+ text = str(e)
+ print self.name, ": get_ip_from_dhcp subprocess Exception", text
+ content = None
+ else:
+ try:
+ if not self.ssh_conn:
+ self.ssh_connect()
+ command = 'get_dhcp_lease.sh ' + mac_address
+ (_, stdout, _) = self.ssh_conn.exec_command(command)
+ content = stdout.read()
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": get_ip_from_dhcp: ssh_Exception:", text
+ content = None
+ self.ssh_conn = None
+ except Exception as e:
+ text = str(e)
+ print self.name, ": get_ip_from_dhcp: Exception:", text
+ content = None
+ self.ssh_conn = None
+
+ if content:
+ self.mac_status[mac_address]["ip"] = content
+ #modify Database
+ self.db_lock.acquire()
+ r,c = self.db.update_rows("ports", {"ip_address": content}, {"mac": mac_address})
+ self.db_lock.release()
+ if r<0:
+ print self.name, ": Database update error:", c
+ else:
+ self.mac_status[mac_address]["retries"] = 0
+ self.mac_status[mac_address]["next_reading"] = (int(now)/3600 +1)* 36000 # 10 hores
+ if self.mac_status[mac_address]["next_reading"] < next_iteration:
+ next_iteration = self.mac_status[mac_address]["next_reading"]
+ print self.name, "mac %s >> %s" % (mac_address, content)
+ continue
+ #a fail has happen
+ self.mac_status[mac_address]["retries"] +=1
+ #next iteration is every 2sec at the beginning; every 5sec after a minute, every 1min after a 5min
+ if now - self.mac_status[mac_address]["active"] > 120:
+ #modify Database to tell openmano that we can not get dhcp from the machine
+ if not self.mac_status[mac_address].get("ip"):
+ self.db_lock.acquire()
+ r,c = self.db.update_rows("ports", {"ip_address": "0.0.0.0"}, {"mac": mac_address})
+ self.db_lock.release()
+ self.mac_status[mac_address]["ip"] = "0.0.0.0"
+ print self.name, "mac %s >> set to 0.0.0.0 because of timeout" % (mac_address)
+
+ if now - self.mac_status[mac_address]["active"] > 60:
+ self.mac_status[mac_address]["next_reading"] = (int(now)/6 +1)* 6
+ elif now - self.mac_status[mac_address]["active"] > 300:
+ self.mac_status[mac_address]["next_reading"] = (int(now)/60 +1)* 60
+ else:
+ self.mac_status[mac_address]["next_reading"] = (int(now)/2 +1)* 2
+
+ if self.mac_status[mac_address]["next_reading"] < next_iteration:
+ next_iteration = self.mac_status[mac_address]["next_reading"]
+ return next_iteration
+
+ def get_fake_ip(self):
+ fake_ip= "192.168.%d.%d" % (random.randint(1,254), random.randint(1,254) )
+ while True:
+ #check not already provided
+ already_used = False
+ for mac_address in self.mac_status:
+ if self.mac_status[mac_address]["ip"] == fake_ip:
+ already_used = True
+ break
+ if not already_used:
+ return fake_ip
+
+
+#EXAMPLE of bash script that must be available at the DHCP server for "isc-dhcp-server" type
+# $ cat ./get_dhcp_lease.sh
+# #!/bin/bash
+# awk '
+# ($1=="lease" && $3=="{"){ lease=$2; active="no"; found="no" }
+# ($1=="binding" && $2=="state" && $3=="active;"){ active="yes" }
+# ($1=="hardware" && $2=="ethernet" && $3==tolower("'$1';")){ found="yes" }
+# ($1=="client-hostname"){ name=$2 }
+# ($1=="}"){ if (active=="yes" && found=="yes"){ target_lease=lease; target_name=name}}
+# END{printf("%s", target_lease)} #print target_name
+# ' /var/lib/dhcp/dhcpd.leases
+
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Implement the plugging for floodligth openflow controller
+It creates the class OF_conn to create dataplane connections
+with static rules based on packet destination MAC address
+'''
+
+__author__="Pablo Montes, Alfonso Tierno"
+__date__ ="$28-oct-2014 12:07:15$"
+
+
+import json
+import requests
+import logging
+
+class OF_conn():
+ ''' Openflow Connector for Floodlight.
+ No MAC learning is used
+ version 0.9 or 1.X is autodetected
+ version 1.X is in progress, not finished!!!
+ '''
+ def __init__(self, params):
+ ''' Constructor.
+ params is a dictionay with the following keys:
+ of_dpid: DPID to use for this controller
+ of_ip: controller IP address
+ of_port: controller TCP port
+ of_version: version, can be "0.9" or "1.X". By default it is autodetected
+ of_debug: debug level for logging. Default to ERROR
+ other keys are ignored
+ Raise an exception if same parameter is missing or wrong
+ '''
+ #check params
+ if "of_ip" not in params or params["of_ip"]==None or "of_port" not in params or params["of_port"]==None:
+ raise ValueError("IP address and port must be provided")
+
+ self.name = "Floodlight"
+ self.dpid = str(params["of_dpid"])
+ self.url = "http://%s:%s" %( str(params["of_ip"]), str(params["of_port"]) )
+
+ self.pp2ofi={} # From Physical Port to OpenFlow Index
+ self.ofi2pp={} # From OpenFlow Index to Physical Port
+ self.headers = {'content-type':'application/json', 'Accept':'application/json'}
+ self.version= None
+ self.logger = logging.getLogger('vim.OF.FL')
+ self.logger.setLevel( getattr(logging, params.get("of_debug", "ERROR") ) )
+ self._set_version(params.get("of_version") )
+
+ def _set_version(self, version):
+ '''set up a version of the controller.
+ Depending on the version it fills the self.ver_names with the naming used in this version
+ '''
+ #static version names
+ if version==None:
+ self.version= None
+ elif version=="0.9":
+ self.version= version
+ self.name = "Floodlightv0.9"
+ self.ver_names={
+ "dpid": "dpid",
+ "URLmodifier": "staticflowentrypusher",
+ "destmac": "dst-mac",
+ "vlanid": "vlan-id",
+ "inport": "ingress-port",
+ "setvlan": "set-vlan-id",
+ "stripvlan": "strip-vlan",
+ }
+ elif version[0]=="1" : #version 1.X
+ self.version= version
+ self.name = "Floodlightv1.X"
+ self.ver_names={
+ "dpid": "switchDPID",
+ "URLmodifier": "staticflowpusher",
+ "destmac": "eth_dst",
+ "vlanid": "eth_vlan_vid",
+ "inport": "in_port",
+ "setvlan": "set_vlan_vid",
+ "stripvlan": "strip_vlan",
+ }
+ else:
+ raise ValueError("Invalid version for floodlight controller")
+
+ def get_of_switches(self):
+ ''' Obtain a a list of switches or DPID detected by this controller
+ Return
+ >=0, list: list length, and a list where each element a tuple pair (DPID, IP address)
+ <0, text_error: if fails
+ '''
+ try:
+ of_response = requests.get(self.url+"/wm/core/controller/switches/json", headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("get_of_switches " + error_text)
+ return -1 , error_text
+ self.logger.debug("get_of_switches " + error_text)
+ info = of_response.json()
+ if type(info) != list and type(info) != tuple:
+ self.logger.error("get_of_switches. Unexpected response not a list %s", str(type(info)))
+ return -1, "Unexpected response, not a list. Wrong version?"
+ if len(info)==0:
+ return 0, info
+ #autodiscover version
+ if self.version == None:
+ if 'dpid' in info[0] and 'inetAddress' in info[0]:
+ self._set_version("0.9")
+ elif 'switchDPID' in info[0] and 'inetAddress' in info[0]:
+ self._set_version("1.X")
+ else:
+ self.logger.error("get_of_switches. Unexpected response, not found 'dpid' or 'switchDPID' field: %s", str(info[0]))
+ return -1, "Unexpected response, not found 'dpid' or 'switchDPID' field. Wrong version?"
+
+ switch_list=[]
+ for switch in info:
+ switch_list.append( (switch[ self.ver_names["dpid"] ], switch['inetAddress']) )
+ return len(switch_list), switch_list
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("get_of_switches " + error_text)
+ return -1, error_text
+
+ def get_of_rules(self, translate_of_ports=True):
+ ''' Obtain the rules inserted at openflow controller
+ Params:
+ translate_of_ports: if True it translates ports from openflow index to physical switch name
+ Return:
+ 0, dict if ok: with the rule name as key and value is another dictionary with the following content:
+ priority: rule priority
+ name: rule name (present also as the master dict key)
+ ingress_port: match input port of the rule
+ dst_mac: match destination mac address of the rule, can be missing or None if not apply
+ vlan_id: match vlan tag of the rule, can be missing or None if not apply
+ actions: list of actions, composed by a pair tuples:
+ (vlan, None/int): for stripping/setting a vlan tag
+ (out, port): send to this port
+ switch: DPID, all
+ -1, text_error if fails
+ '''
+
+ #get translation, autodiscover version
+ if len(self.ofi2pp) == 0:
+ r,c = self.obtain_port_correspondence()
+ if r<0:
+ return r,c
+ #get rules
+ try:
+ of_response = requests.get(self.url+"/wm/%s/list/%s/json" %(self.ver_names["URLmodifier"], self.dpid),
+ headers=self.headers)
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("get_of_rules " + error_text)
+ return -1 , error_text
+ self.logger.debug("get_of_rules " + error_text)
+ info = of_response.json()
+ if type(info) != dict:
+ self.logger.error("get_of_rules. Unexpected response not a dict %s", str(type(info)))
+ return -1, "Unexpected response, not a dict. Wrong version?"
+ rule_dict={}
+ for switch,switch_info in info.iteritems():
+ if switch_info == None:
+ continue
+ if str(switch) != self.dpid:
+ continue
+ for name,details in switch_info.iteritems():
+ rule = {}
+ rule["switch"] = str(switch)
+ #rule["active"] = "true"
+ rule["priority"] = int(details["priority"])
+ if self.version[0]=="0":
+ if translate_of_ports:
+ rule["ingress_port"] = self.ofi2pp[ details["match"]["inputPort"] ]
+ else:
+ rule["ingress_port"] = str(details["match"]["inputPort"])
+ dst_mac = details["match"]["dataLayerDestination"]
+ if dst_mac != "00:00:00:00:00:00":
+ rule["dst_mac"] = dst_mac
+ vlan = details["match"]["dataLayerVirtualLan"]
+ if vlan != -1:
+ rule["vlan_id"] = vlan
+ actionlist=[]
+ for action in details["actions"]:
+ if action["type"]=="OUTPUT":
+ if translate_of_ports:
+ port = self.ofi2pp[ action["port"] ]
+ else:
+ port = action["port"]
+ actionlist.append( ("out", port) )
+ elif action["type"]=="STRIP_VLAN":
+ actionlist.append( ("vlan",None) )
+ elif action["type"]=="SET_VLAN_ID":
+ actionlist.append( ("vlan", action["virtualLanIdentifier"]) )
+ else:
+ actionlist.append( (action["type"], str(action) ))
+ self.logger.warning("get_of_rules() Unknown action in rule %s: %s", rule["name"], str(action))
+ rule["actions"] = actionlist
+ elif self.version[0]=="1":
+ if translate_of_ports:
+ rule["ingress_port"] = self.ofi2pp[ details["match"]["in_port"] ]
+ else:
+ rule["ingress_port"] = details["match"]["in_port"]
+ if "eth_dst" in details["match"]:
+ dst_mac = details["match"]["eth_dst"]
+ if dst_mac != "00:00:00:00:00:00":
+ rule["dst_mac"] = dst_mac
+ if "eth_vlan_vid" in details["match"]:
+ vlan = int(details["match"]["eth_vlan_vid"],16) & 0xFFF
+ rule["vlan_id"] = str(vlan)
+ actionlist=[]
+ for action in details["instructions"]["instruction_apply_actions"]:
+ if action=="output":
+ if translate_of_ports:
+ port = self.ofi2pp[ details["instructions"]["instruction_apply_actions"]["output"] ]
+ else:
+ port = details["instructions"]["instruction_apply_actions"]["output"]
+ actionlist.append( ("out",port) )
+ elif action=="strip_vlan":
+ actionlist.append( ("vlan",None) )
+ elif action=="set_vlan_vid":
+ actionlist.append( ("vlan", details["instructions"]["instruction_apply_actions"]["set_vlan_vid"]) )
+ else:
+ self.logger.error("get_of_rules Unknown action in rule %s: %s", rule["name"], str(action))
+ #actionlist.append( (action, str(details["instructions"]["instruction_apply_actions"]) ))
+ rule_dict[str(name)] = rule
+ return 0, rule_dict
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("get_of_rules " + error_text)
+ return -1, error_text
+
+ def obtain_port_correspondence(self):
+ '''Obtain the correspondence between physical and openflow port names
+ return:
+ 0, dictionary: with physical name as key, openflow name as value
+ -1, error_text: if fails
+ '''
+ try:
+ of_response = requests.get(self.url+"/wm/core/controller/switches/json", headers=self.headers)
+ #print vim_response.status_code
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("obtain_port_correspondence " + error_text)
+ return -1 , error_text
+ self.logger.debug("obtain_port_correspondence " + error_text)
+ info = of_response.json()
+
+ if type(info) != list and type(info) != tuple:
+ return -1, "unexpected openflow response, not a list. Wrong version?"
+
+ index = -1
+ if len(info)>0:
+ #autodiscover version
+ if self.version == None:
+ if 'dpid' in info[0] and 'ports' in info[0]:
+ self._set_version("0.9")
+ elif 'switchDPID' in info[0]:
+ self._set_version("1.X")
+ else:
+ return -1, "unexpected openflow response, Wrong version?"
+
+ for i in range(0,len(info)):
+ if info[i][ self.ver_names["dpid"] ] == self.dpid:
+ index = i
+ break
+ if index == -1:
+ text = "DPID '"+self.dpid+"' not present in controller "+self.url
+ #print self.name, ": get_of_controller_info ERROR", text
+ return -1, text
+ else:
+ if self.version[0]=="0":
+ ports = info[index]["ports"]
+ else: #version 1.X
+ of_response = requests.get(self.url+"/wm/core/switch/%s/port-desc/json" %self.dpid, headers=self.headers)
+ #print vim_response.status_code
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("obtain_port_correspondence " + error_text)
+ return -1 , error_text
+ self.logger.debug("obtain_port_correspondence " + error_text)
+ info = of_response.json()
+ if type(info) != dict:
+ return -1, "unexpected openflow port-desc response, not a dict. Wrong version?"
+ if "portDesc" not in info:
+ return -1, "unexpected openflow port-desc response, 'portDesc' not found. Wrong version?"
+ if type(info["portDesc"]) != list and type(info["portDesc"]) != tuple:
+ return -1, "unexpected openflow port-desc response at 'portDesc', not a list. Wrong version?"
+ ports = info["portDesc"]
+ for port in ports:
+ self.pp2ofi[ str(port["name"]) ] = str(port["portNumber"] )
+ self.ofi2pp[ port["portNumber"]] = str(port["name"])
+ #print self.name, ": get_of_controller_info ports:", self.pp2ofi
+ return 0, self.pp2ofi
+ except (requests.exceptions.RequestException, ValueError) as e:
+ #ValueError in the case that JSON can not be decoded
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("obtain_port_correspondence " + error_text)
+ return -1, error_text
+
+ def del_flow(self, flow_name):
+ ''' Delete an existing rule
+ Params: flow_name, this is the rule name
+ Return
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ #autodiscover version
+ if self.version == None:
+ r,c = self.get_of_switches()
+ if r<0:
+ return r,c
+ elif r==0:
+ return -1, "No dpid found "
+ try:
+ of_response = requests.delete(self.url+"/wm/%s/json" % self.ver_names["URLmodifier"],
+ headers=self.headers, data='{"switch":"%s","name":"%s"}' %(self.dpid, flow_name)
+ )
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("del_flow " + error_text)
+ return -1 , error_text
+ self.logger.debug("del_flow OK " + error_text)
+ return 0, None
+
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("del_flow " + error_text)
+ return -1, error_text
+
+ def new_flow(self, data):
+ ''' Insert a new static rule
+ Params: data: dictionary with the following content:
+ priority: rule priority
+ name: rule name
+ ingress_port: match input port of the rule
+ dst_mac: match destination mac address of the rule, missing or None if not apply
+ vlan_id: match vlan tag of the rule, missing or None if not apply
+ actions: list of actions, composed by a pair tuples with these posibilities:
+ ('vlan', None/int): for stripping/setting a vlan tag
+ ('out', port): send to this port
+ Return
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ #get translation, autodiscover version
+ if len(self.pp2ofi) == 0:
+ r,c = self.obtain_port_correspondence()
+ if r<0:
+ return r,c
+ try:
+ #We have to build the data for the floodlight call from the generic data
+ sdata = {'active': "true", "name":data["name"]}
+ if data.get("priority"):
+ sdata["priority"] = str(data["priority"])
+ if data.get("vlan_id"):
+ sdata[ self.ver_names["vlanid"] ] = data["vlan_id"]
+ if data.get("dst_mac"):
+ sdata[ self.ver_names["destmac"] ] = data["dst_mac"]
+ sdata['switch'] = self.dpid
+ if not data['ingress_port'] in self.pp2ofi:
+ error_text = 'Error. Port '+data['ingress_port']+' is not present in the switch'
+ self.logger.warning("new_flow " + error_text)
+ return -1, error_text
+
+ sdata[ self.ver_names["inport"] ] = self.pp2ofi[data['ingress_port']]
+ sdata['actions'] = ""
+
+ for action in data['actions']:
+ if len(sdata['actions']) > 0:
+ sdata['actions'] += ','
+ if action[0] == "vlan":
+ if action[1]==None:
+ sdata['actions'] += self.ver_names["stripvlan"]
+ else:
+ sdata['actions'] += self.ver_names["setvlan"] + "=" + str(action[1])
+ elif action[0] == 'out':
+ sdata['actions'] += "output=" + self.pp2ofi[ action[1] ]
+
+
+ of_response = requests.post(self.url+"/wm/%s/json" % self.ver_names["URLmodifier"],
+ headers=self.headers, data=json.dumps(sdata) )
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code != 200:
+ self.logger.warning("new_flow " + error_text)
+ return -1 , error_text
+ self.logger.debug("new_flow OK" + error_text)
+ return 0, None
+
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("new_flow " + error_text)
+ return -1, error_text
+
+ def clear_all_flows(self):
+ ''' Delete all existing rules
+ Return:
+ 0, None if ok
+ -1, text_error if fails
+ '''
+ #autodiscover version
+ if self.version == None:
+ r,c = self.get_of_switches()
+ if r<0:
+ return r,c
+ elif r==0: #empty
+ return 0, None
+ try:
+ url = self.url+"/wm/%s/clear/%s/json" % (self.ver_names["URLmodifier"], self.dpid)
+ of_response = requests.get(url )
+ error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text)
+ if of_response.status_code < 200 or of_response.status_code >= 300:
+ self.logger.warning("clear_all_flows " + error_text)
+ return -1 , error_text
+ self.logger.debug("clear_all_flows OK " + error_text)
+ return 0, None
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ self.logger.error("clear_all_flows " + error_text)
+ return -1, error_text
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is thread that interact with the host and the libvirt to manage VM
+One thread will be launched per host
+'''
+__author__="Pablo Montes, Alfonso Tierno"
+__date__ ="$10-jul-2014 12:07:15$"
+
+
+import json
+import yaml
+import threading
+import time
+import Queue
+import paramiko
+from jsonschema import validate as js_v, exceptions as js_e
+import libvirt
+from vim_schema import localinfo_schema, hostinfo_schema
+import random
+#from logging import Logger
+#import utils.auxiliary_functions as af
+
+#TODO: insert a logging system
+
+class host_thread(threading.Thread):
+ def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode, develop_bridge_iface):
+ '''Init a thread.
+ Arguments:
+ 'id' number of thead
+ 'name' name of thread
+ 'host','user': host ip or name to manage and user
+ 'db', 'db_lock': database class and lock to use it in exclusion
+ '''
+ threading.Thread.__init__(self)
+ self.name = name
+ self.host = host
+ self.user = user
+ self.db = db
+ self.db_lock = db_lock
+ self.test = test
+ self.develop_mode = develop_mode
+ self.develop_bridge_iface = develop_bridge_iface
+ self.image_path = image_path
+ self.host_id = host_id
+ self.version = version
+
+ self.xml_level = 0
+ #self.pending ={}
+
+ self.server_status = {} #dictionary with pairs server_uuid:server_status
+ self.pending_terminate_server =[] #list with pairs (time,server_uuid) time to send a terminate for a server being destroyed
+ self.next_update_server_status = 0 #time when must be check servers status
+
+ self.hostinfo = None
+
+ self.queueLock = threading.Lock()
+ self.taskQueue = Queue.Queue(2000)
+
+ def ssh_connect(self):
+ try:
+ #Connect SSH
+ self.ssh_conn = paramiko.SSHClient()
+ self.ssh_conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.ssh_conn.load_system_host_keys()
+ self.ssh_conn.connect(self.host, username=self.user, timeout=10) #, None)
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": ssh_connect ssh Exception:", text
+
+ def load_localinfo(self):
+ if not self.test:
+ try:
+ #Connect SSH
+ self.ssh_connect()
+
+ command = 'mkdir -p ' + self.image_path
+ #print self.name, ': command:', command
+ (_, stdout, stderr) = self.ssh_conn.exec_command(command)
+ content = stderr.read()
+ if len(content) > 0:
+ print self.name, ': command:', command, "stderr:", content
+
+ command = 'cat ' + self.image_path + '/.openvim.yaml'
+ #print self.name, ': command:', command
+ (_, stdout, stderr) = self.ssh_conn.exec_command(command)
+ content = stdout.read()
+ if len(content) == 0:
+ print self.name, ': command:', command, "stderr:", stderr.read()
+ raise paramiko.ssh_exception.SSHException("Error empty file ")
+ self.localinfo = yaml.load(content)
+ js_v(self.localinfo, localinfo_schema)
+ self.localinfo_dirty=False
+ if 'server_files' not in self.localinfo:
+ self.localinfo['server_files'] = {}
+ print self.name, ': localinfo load from host'
+ return
+
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": load_localinfo ssh Exception:", text
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ print self.name, ": load_localinfo libvirt Exception:", text
+ except yaml.YAMLError as exc:
+ text = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ text = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print self.name, ": load_localinfo yaml format Exception", text
+ except js_e.ValidationError as e:
+ text = ""
+ if len(e.path)>0: text=" at '" + ":".join(map(str, e.path))+"'"
+ print self.name, ": load_localinfo format Exception:", text, e.message
+ except Exception as e:
+ text = str(e)
+ print self.name, ": load_localinfo Exception:", text
+
+ #not loaded, insert a default data and force saving by activating dirty flag
+ self.localinfo = {'files':{}, 'server_files':{} }
+ #self.localinfo_dirty=True
+ self.localinfo_dirty=False
+
+ def load_hostinfo(self):
+ if self.test:
+ return;
+ try:
+ #Connect SSH
+ self.ssh_connect()
+
+
+ command = 'cat ' + self.image_path + '/hostinfo.yaml'
+ #print self.name, ': command:', command
+ (_, stdout, stderr) = self.ssh_conn.exec_command(command)
+ content = stdout.read()
+ if len(content) == 0:
+ print self.name, ': command:', command, "stderr:", stderr.read()
+ raise paramiko.ssh_exception.SSHException("Error empty file ")
+ self.hostinfo = yaml.load(content)
+ js_v(self.hostinfo, hostinfo_schema)
+ print self.name, ': hostlinfo load from host', self.hostinfo
+ return
+
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": load_hostinfo ssh Exception:", text
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ print self.name, ": load_hostinfo libvirt Exception:", text
+ except yaml.YAMLError as exc:
+ text = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ text = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print self.name, ": load_hostinfo yaml format Exception", text
+ except js_e.ValidationError as e:
+ text = ""
+ if len(e.path)>0: text=" at '" + ":".join(map(str, e.path))+"'"
+ print self.name, ": load_hostinfo format Exception:", text, e.message
+ except Exception as e:
+ text = str(e)
+ print self.name, ": load_hostinfo Exception:", text
+
+ #not loaded, insert a default data
+ self.hostinfo = None
+
+ def save_localinfo(self, tries=3):
+ if self.test:
+ self.localinfo_dirty = False
+ return
+
+ while tries>=0:
+ tries-=1
+
+ try:
+ command = 'cat > ' + self.image_path + '/.openvim.yaml'
+ print self.name, ': command:', command
+ (stdin, _, _) = self.ssh_conn.exec_command(command)
+ yaml.safe_dump(self.localinfo, stdin, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True)
+ self.localinfo_dirty = False
+ break #while tries
+
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": save_localinfo ssh Exception:", text
+ if "SSH session not active" in text:
+ self.ssh_connect()
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ print self.name, ": save_localinfo libvirt Exception:", text
+ except yaml.YAMLError as exc:
+ text = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ text = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print self.name, ": save_localinfo yaml format Exception", text
+ except Exception as e:
+ text = str(e)
+ print self.name, ": save_localinfo Exception:", text
+
+ def load_servers_from_db(self):
+ self.db_lock.acquire()
+ r,c = self.db.get_table(SELECT=('uuid','status', 'image_id'), FROM='instances', WHERE={'host_id': self.host_id})
+ self.db_lock.release()
+
+ self.server_status = {}
+ if r<0:
+ print self.name, ": Error getting data from database:", c
+ return
+ for server in c:
+ self.server_status[ server['uuid'] ] = server['status']
+
+ #convert from old version to new one
+ if 'inc_files' in self.localinfo and server['uuid'] in self.localinfo['inc_files']:
+ server_files_dict = {'source file': self.localinfo['inc_files'][ server['uuid'] ] [0], 'file format':'raw' }
+ if server_files_dict['source file'][-5:] == 'qcow2':
+ server_files_dict['file format'] = 'qcow2'
+
+ self.localinfo['server_files'][ server['uuid'] ] = { server['image_id'] : server_files_dict }
+ if 'inc_files' in self.localinfo:
+ del self.localinfo['inc_files']
+ self.localinfo_dirty = True
+
+ def delete_unused_files(self):
+ '''Compares self.localinfo['server_files'] content with real servers running self.server_status obtained from database
+ Deletes unused entries at self.loacalinfo and the corresponding local files.
+ The only reason for this mismatch is the manual deletion of instances (VM) at database
+ '''
+ if self.test:
+ return
+ for uuid,images in self.localinfo['server_files'].items():
+ if uuid not in self.server_status:
+ for localfile in images.values():
+ try:
+ print self.name, ": deleting file '%s' of unused server '%s'" %(localfile['source file'], uuid)
+ self.delete_file(localfile['source file'])
+ except paramiko.ssh_exception.SSHException as e:
+ print self.name, ": Exception deleting file '%s': %s" %(localfile['source file'], str(e))
+ del self.localinfo['server_files'][uuid]
+ self.localinfo_dirty = True
+
+ def insert_task(self, task, *aditional):
+ try:
+ self.queueLock.acquire()
+ task = self.taskQueue.put( (task,) + aditional, timeout=5)
+ self.queueLock.release()
+ return 1, None
+ except Queue.Full:
+ return -1, "timeout inserting a task over host " + self.name
+
+ def run(self):
+ while True:
+ self.load_localinfo()
+ self.load_hostinfo()
+ self.load_servers_from_db()
+ self.delete_unused_files()
+ while True:
+ self.queueLock.acquire()
+ if not self.taskQueue.empty():
+ task = self.taskQueue.get()
+ else:
+ task = None
+ self.queueLock.release()
+
+ if task is None:
+ now=time.time()
+ if self.localinfo_dirty:
+ self.save_localinfo()
+ elif self.next_update_server_status < now:
+ self.update_servers_status()
+ self.next_update_server_status = now + 5
+ elif len(self.pending_terminate_server)>0 and self.pending_terminate_server[0][0]<now:
+ self.server_forceoff()
+ else:
+ time.sleep(1)
+ continue
+
+ if task[0] == 'instance':
+ print self.name, ": processing task instance", task[1]['action']
+ retry=0
+ while retry <2:
+ retry += 1
+ r=self.action_on_server(task[1], retry==2)
+ if r>=0:
+ break
+ elif task[0] == 'image':
+ pass
+ elif task[0] == 'exit':
+ print self.name, ": processing task exit"
+ self.terminate()
+ return 0
+ elif task[0] == 'reload':
+ print self.name, ": processing task reload terminating and relaunching"
+ self.terminate()
+ break
+ elif task[0] == 'edit-iface':
+ print self.name, ": processing task edit-iface port=%s, old_net=%s, new_net=%s" % (task[1], task[2], task[3])
+ self.edit_iface(task[1], task[2], task[3])
+ elif task[0] == 'restore-iface':
+ print self.name, ": processing task restore-iface %s mac=%s" % (task[1], task[2])
+ self.restore_iface(task[1], task[2])
+ else:
+ print self.name, ": unknown task", task
+
+ def server_forceoff(self, wait_until_finished=False):
+ while len(self.pending_terminate_server)>0:
+ now = time.time()
+ if self.pending_terminate_server[0][0]>now:
+ if wait_until_finished:
+ time.sleep(1)
+ continue
+ else:
+ return
+ req={'uuid':self.pending_terminate_server[0][1],
+ 'action':{'terminate':'force'},
+ 'status': None
+ }
+ self.action_on_server(req)
+ self.pending_terminate_server.pop(0)
+
+ def terminate(self):
+ try:
+ self.server_forceoff(True)
+ if self.localinfo_dirty:
+ self.save_localinfo()
+ if not self.test:
+ self.ssh_conn.close()
+ except Exception as e:
+ text = str(e)
+ print self.name, ": terminate Exception:", text
+ print self.name, ": exit from host_thread"
+
+ def get_local_iface_name(self, generic_name):
+ if self.hostinfo != None and "iface_names" in self.hostinfo and generic_name in self.hostinfo["iface_names"]:
+ return self.hostinfo["iface_names"][generic_name]
+ return generic_name
+
+ def create_xml_server(self, server, dev_list, server_metadata={}):
+ """Function that implements the generation of the VM XML definition.
+ Additional devices are in dev_list list
+ The main disk is upon dev_list[0]"""
+
+ #get if operating system is Windows
+ windows_os = False
+ os_type = server_metadata.get('os_type', None)
+ if os_type == None and 'metadata' in dev_list[0]:
+ os_type = dev_list[0]['metadata'].get('os_type', None)
+ if os_type != None and os_type.lower() == "windows":
+ windows_os = True
+ #get type of hard disk bus
+ bus_ide = True if windows_os else False
+ bus = server_metadata.get('bus', None)
+ if bus == None and 'metadata' in dev_list[0]:
+ bus = dev_list[0]['metadata'].get('bus', None)
+ if bus != None:
+ bus_ide = True if bus=='ide' else False
+
+ self.xml_level = 0
+
+ text = "<domain type='kvm'>"
+ #get topology
+ topo = server_metadata.get('topology', None)
+ if topo == None and 'metadata' in dev_list[0]:
+ topo = dev_list[0]['metadata'].get('topology', None)
+ #name
+ name = server.get('name','') + "_" + server['uuid']
+ name = name[:58] #qemu impose a length limit of 59 chars or not start. Using 58
+ text += self.inc_tab() + "<name>" + name+ "</name>"
+ #uuid
+ text += self.tab() + "<uuid>" + server['uuid'] + "</uuid>"
+
+ numa={}
+ if 'extended' in server and server['extended']!=None and 'numas' in server['extended']:
+ numa = server['extended']['numas'][0]
+ #memory
+ use_huge = False
+ memory = int(numa.get('memory',0))*1024*1024 #in KiB
+ if memory==0:
+ memory = int(server['ram'])*1024;
+ else:
+ if not self.develop_mode:
+ use_huge = True
+ if memory==0:
+ return -1, 'No memory assigned to instance'
+ memory = str(memory)
+ text += self.tab() + "<memory unit='KiB'>" +memory+"</memory>"
+ text += self.tab() + "<currentMemory unit='KiB'>" +memory+ "</currentMemory>"
+ if use_huge:
+ text += self.tab()+'<memoryBacking>'+ \
+ self.inc_tab() + '<hugepages/>'+ \
+ self.dec_tab()+ '</memoryBacking>'
+
+ #cpu
+ use_cpu_pinning=False
+ vcpus = int(server.get("vcpus",0))
+ cpu_pinning = []
+ if 'cores-source' in numa:
+ use_cpu_pinning=True
+ for index in range(0, len(numa['cores-source'])):
+ cpu_pinning.append( [ numa['cores-id'][index], numa['cores-source'][index] ] )
+ vcpus += 1
+ if 'threads-source' in numa:
+ use_cpu_pinning=True
+ for index in range(0, len(numa['threads-source'])):
+ cpu_pinning.append( [ numa['threads-id'][index], numa['threads-source'][index] ] )
+ vcpus += 1
+ if 'paired-threads-source' in numa:
+ use_cpu_pinning=True
+ for index in range(0, len(numa['paired-threads-source'])):
+ cpu_pinning.append( [numa['paired-threads-id'][index][0], numa['paired-threads-source'][index][0] ] )
+ cpu_pinning.append( [numa['paired-threads-id'][index][1], numa['paired-threads-source'][index][1] ] )
+ vcpus += 2
+
+ if use_cpu_pinning and not self.develop_mode:
+ text += self.tab()+"<vcpu placement='static'>" +str(len(cpu_pinning)) +"</vcpu>" + \
+ self.tab()+'<cputune>'
+ self.xml_level += 1
+ for i in range(0, len(cpu_pinning)):
+ text += self.tab() + "<vcpupin vcpu='" +str(cpu_pinning[i][0])+ "' cpuset='" +str(cpu_pinning[i][1]) +"'/>"
+ text += self.dec_tab()+'</cputune>'+ \
+ self.tab() + '<numatune>' +\
+ self.inc_tab() + "<memory mode='strict' nodeset='" +str(numa['source'])+ "'/>" +\
+ self.dec_tab() + '</numatune>'
+ else:
+ if vcpus==0:
+ return -1, "Instance without number of cpus"
+ text += self.tab()+"<vcpu>" + str(vcpus) + "</vcpu>"
+
+ #boot
+ boot_cdrom = False
+ for dev in dev_list:
+ if dev['type']=='cdrom' :
+ boot_cdrom = True
+ break
+ text += self.tab()+ '<os>' + \
+ self.inc_tab() + "<type arch='x86_64' machine='pc'>hvm</type>"
+ if boot_cdrom:
+ text += self.tab() + "<boot dev='cdrom'/>"
+ text += self.tab() + "<boot dev='hd'/>" + \
+ self.dec_tab()+'</os>'
+ #features
+ text += self.tab()+'<features>'+\
+ self.inc_tab()+'<acpi/>' +\
+ self.tab()+'<apic/>' +\
+ self.tab()+'<pae/>'+ \
+ self.dec_tab() +'</features>'
+ if windows_os or topo=="oneSocket":
+ text += self.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='1' /> </cpu>"% vcpus
+ else:
+ text += self.tab() + "<cpu mode='host-model'></cpu>"
+ text += self.tab() + "<clock offset='utc'/>" +\
+ self.tab() + "<on_poweroff>preserve</on_poweroff>" + \
+ self.tab() + "<on_reboot>restart</on_reboot>" + \
+ self.tab() + "<on_crash>restart</on_crash>"
+ text += self.tab() + "<devices>" + \
+ self.inc_tab() + "<emulator>/usr/libexec/qemu-kvm</emulator>" + \
+ self.tab() + "<serial type='pty'>" +\
+ self.inc_tab() + "<target port='0'/>" + \
+ self.dec_tab() + "</serial>" +\
+ self.tab() + "<console type='pty'>" + \
+ self.inc_tab()+ "<target type='serial' port='0'/>" + \
+ self.dec_tab()+'</console>'
+ if windows_os:
+ text += self.tab() + "<controller type='usb' index='0'/>" + \
+ self.tab() + "<controller type='ide' index='0'/>" + \
+ self.tab() + "<input type='mouse' bus='ps2'/>" + \
+ self.tab() + "<sound model='ich6'/>" + \
+ self.tab() + "<video>" + \
+ self.inc_tab() + "<model type='cirrus' vram='9216' heads='1'/>" + \
+ self.dec_tab() + "</video>" + \
+ self.tab() + "<memballoon model='virtio'/>" + \
+ self.tab() + "<input type='tablet' bus='usb'/>" #TODO revisar
+
+#> self.tab()+'<alias name=\'hostdev0\'/>\n' +\
+#> self.dec_tab()+'</hostdev>\n' +\
+#> self.tab()+'<input type=\'tablet\' bus=\'usb\'/>\n'
+ if windows_os:
+ text += self.tab() + "<graphics type='vnc' port='-1' autoport='yes'/>"
+ else:
+ #If image contains 'GRAPH' include graphics
+ #if 'GRAPH' in image:
+ text += self.tab() + "<graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>" +\
+ self.inc_tab() + "<listen type='address' address='0.0.0.0'/>" +\
+ self.dec_tab() + "</graphics>"
+
+ vd_index = 'a'
+ for dev in dev_list:
+ bus_ide_dev = bus_ide
+ if dev['type']=='cdrom' or dev['type']=='disk':
+ if dev['type']=='cdrom':
+ bus_ide_dev = True
+ text += self.tab() + "<disk type='file' device='"+dev['type']+"'>"
+ if 'file format' in dev:
+ text += self.inc_tab() + "<driver name='qemu' type='" +dev['file format']+ "' cache='writethrough'/>"
+ if 'source file' in dev:
+ text += self.tab() + "<source file='" +dev['source file']+ "'/>"
+ #elif v['type'] == 'block':
+ # text += self.tab() + "<source dev='" + v['source'] + "'/>"
+ #else:
+ # return -1, 'Unknown disk type ' + v['type']
+ vpci = dev.get('vpci',None)
+ if vpci == None:
+ vpci = dev['metadata'].get('vpci',None)
+ text += self.pci2xml(vpci)
+
+ if bus_ide_dev:
+ text += self.tab() + "<target dev='hd" +vd_index+ "' bus='ide'/>" #TODO allows several type of disks
+ else:
+ text += self.tab() + "<target dev='vd" +vd_index+ "' bus='virtio'/>"
+ text += self.dec_tab() + '</disk>'
+ vd_index = chr(ord(vd_index)+1)
+ elif dev['type']=='xml':
+ dev_text = dev['xml']
+ if 'vpci' in dev:
+ dev_text = dev_text.replace('__vpci__', dev['vpci'])
+ if 'source file' in dev:
+ dev_text = dev_text.replace('__file__', dev['source file'])
+ if 'file format' in dev:
+ dev_text = dev_text.replace('__format__', dev['source file'])
+ if '__dev__' in dev_text:
+ dev_text = dev_text.replace('__dev__', vd_index)
+ vd_index = chr(ord(vd_index)+1)
+ text += dev_text
+ else:
+ return -1, 'Unknown device type ' + dev['type']
+
+ net_nb=0
+ bridge_interfaces = server.get('networks', [])
+ for v in bridge_interfaces:
+ #Get the brifge name
+ self.db_lock.acquire()
+ result, content = self.db.get_table(FROM='nets', SELECT=('provider',),WHERE={'uuid':v['net_id']} )
+ self.db_lock.release()
+ if result <= 0:
+ print "create_xml_server ERROR getting nets",result, content
+ return -1, content
+ #ALF: Allow by the moment the 'default' bridge net because is confortable for provide internet to VM
+ #I know it is not secure
+ #for v in sorted(desc['network interfaces'].itervalues()):
+ model = v.get("model", None)
+ if content[0]['provider']=='default':
+ text += self.tab() + "<interface type='network'>" + \
+ self.inc_tab() + "<source network='" +content[0]['provider']+ "'/>"
+ elif content[0]['provider'][0:7]=='macvtap':
+ text += self.tab()+"<interface type='direct'>" + \
+ self.inc_tab() + "<source dev='" + self.get_local_iface_name(content[0]['provider'][8:]) + "' mode='bridge'/>" + \
+ self.tab() + "<target dev='macvtap0'/>"
+ if windows_os:
+ text += self.tab() + "<alias name='net" + str(net_nb) + "'/>"
+ elif model==None:
+ model = "virtio"
+ elif content[0]['provider'][0:6]=='bridge':
+ text += self.tab() + "<interface type='bridge'>" + \
+ self.inc_tab()+"<source bridge='" +self.get_local_iface_name(content[0]['provider'][7:])+ "'/>"
+ if windows_os:
+ text += self.tab() + "<target dev='vnet" + str(net_nb)+ "'/>" +\
+ self.tab() + "<alias name='net" + str(net_nb)+ "'/>"
+ elif model==None:
+ model = "virtio"
+ else:
+ return -1, 'Unknown Bridge net provider ' + content[0]['provider']
+ if model!=None:
+ text += self.tab() + "<model type='" +model+ "'/>"
+ if v.get('mac_address', None) != None:
+ text+= self.tab() +"<mac address='" +v['mac_address']+ "'/>"
+ text += self.pci2xml(v.get('vpci',None))
+ text += self.dec_tab()+'</interface>'
+
+ net_nb += 1
+
+ interfaces = numa.get('interfaces', [])
+
+ net_nb=0
+ for v in interfaces:
+ if self.develop_mode: #map these interfaces to bridges
+ text += self.tab() + "<interface type='bridge'>" + \
+ self.inc_tab()+"<source bridge='" +self.develop_bridge_iface+ "'/>"
+ if windows_os:
+ text += self.tab() + "<target dev='vnet" + str(net_nb)+ "'/>" +\
+ self.tab() + "<alias name='net" + str(net_nb)+ "'/>"
+ else:
+ text += self.tab() + "<model type='e1000'/>" #e1000 is more probable to be supported than 'virtio'
+ if v.get('mac_address', None) != None:
+ text+= self.tab() +"<mac address='" +v['mac_address']+ "'/>"
+ text += self.pci2xml(v.get('vpci',None))
+ text += self.dec_tab()+'</interface>'
+ continue
+
+ if v['dedicated'] == 'yes': #passthrought
+ text += self.tab() + "<hostdev mode='subsystem' type='pci' managed='yes'>" + \
+ self.inc_tab() + "<source>"
+ self.inc_tab()
+ text += self.pci2xml(v['source'])
+ text += self.dec_tab()+'</source>'
+ text += self.pci2xml(v.get('vpci',None))
+ if windows_os:
+ text += self.tab() + "<alias name='hostdev" + str(net_nb) + "'/>"
+ text += self.dec_tab()+'</hostdev>'
+ net_nb += 1
+ else: #sriov_interfaces
+ #skip not connected interfaces
+ if v.get("net_id") == None:
+ continue
+ text += self.tab() + "<interface type='hostdev' managed='yes'>"
+ self.inc_tab()
+ if v.get('mac_address', None) != None:
+ text+= self.tab() + "<mac address='" +v['mac_address']+ "'/>"
+ text+= self.tab()+'<source>'
+ self.inc_tab()
+ text += self.pci2xml(v['source'])
+ text += self.dec_tab()+'</source>'
+ if v.get('vlan',None) != None:
+ text += self.tab() + "<vlan> <tag id='" + str(v['vlan']) + "'/> </vlan>"
+ text += self.pci2xml(v.get('vpci',None))
+ if windows_os:
+ text += self.tab() + "<alias name='hostdev" + str(net_nb) + "'/>"
+ text += self.dec_tab()+'</interface>'
+
+
+ text += self.dec_tab()+'</devices>'+\
+ self.dec_tab()+'</domain>'
+ return 0, text
+
+ def pci2xml(self, pci):
+ '''from a pci format text XXXX:XX:XX.X generates the xml content of <address>
+ alows an empty pci text'''
+ if pci is None:
+ return ""
+ first_part = pci.split(':')
+ second_part = first_part[2].split('.')
+ return self.tab() + "<address type='pci' domain='0x" + first_part[0] + \
+ "' bus='0x" + first_part[1] + "' slot='0x" + second_part[0] + \
+ "' function='0x" + second_part[1] + "'/>"
+
+ def tab(self):
+ """Return indentation according to xml_level"""
+ return "\n" + (' '*self.xml_level)
+
+ def inc_tab(self):
+ """Increment and return indentation according to xml_level"""
+ self.xml_level += 1
+ return self.tab()
+
+ def dec_tab(self):
+ """Decrement and return indentation according to xml_level"""
+ self.xml_level -= 1
+ return self.tab()
+
+ def get_file_info(self, path):
+ command = 'ls -lL --time-style=+%Y-%m-%dT%H:%M:%S ' + path
+ print self.name, ': command:', command
+ (_, stdout, _) = self.ssh_conn.exec_command(command)
+ content = stdout.read()
+ if len(content) == 0:
+ return None # file does not exist
+ else:
+ return content.split(" ") #(permission, 1, owner, group, size, date, file)
+
+ def qemu_get_info(self, path):
+ command = 'qemu-img info ' + path
+ print self.name, ': command:', command
+ (_, stdout, stderr) = self.ssh_conn.exec_command(command)
+ content = stdout.read()
+ if len(content) == 0:
+ error = stderr.read()
+ print self.name, ": get_qemu_info error ", error
+ raise paramiko.ssh_exception.SSHException("Error getting qemu_info: " + error)
+ else:
+ try:
+ return yaml.load(content)
+ except yaml.YAMLError as exc:
+ text = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ text = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print self.name, ": get_qemu_info yaml format Exception", text
+ raise paramiko.ssh_exception.SSHException("Error getting qemu_info yaml format" + text)
+
+ def qemu_change_backing(self, inc_file, new_backing_file):
+ command = 'qemu-img rebase -u -b ' + new_backing_file + ' ' + inc_file
+ print self.name, ': command:', command
+ (_, _, stderr) = self.ssh_conn.exec_command(command)
+ content = stderr.read()
+ if len(content) == 0:
+ return 0
+ else:
+ print self.name, ": qemu_change_backing error: ", content
+ return -1
+
+ def get_notused_filename(self, proposed_name, suffix=''):
+ '''Look for a non existing file_name in the host
+ proposed_name: proposed file name, includes path
+ suffix: suffix to be added to the name, before the extention
+ '''
+ extension = proposed_name.rfind(".")
+ slash = proposed_name.rfind("/")
+ if extension < 0 or extension < slash: # no extension
+ extension = len(proposed_name)
+ target_name = proposed_name[:extension] + suffix + proposed_name[extension:]
+ info = self.get_file_info(target_name)
+ if info is None:
+ return target_name
+
+ index=0
+ while info is not None:
+ target_name = proposed_name[:extension] + suffix + "-" + str(index) + proposed_name[extension:]
+ index+=1
+ info = self.get_file_info(target_name)
+ return target_name
+
+ def get_notused_path(self, proposed_path, suffix=''):
+ '''Look for a non existing path at database for images
+ proposed_path: proposed file name, includes path
+ suffix: suffix to be added to the name, before the extention
+ '''
+ extension = proposed_path.rfind(".")
+ if extension < 0:
+ extension = len(proposed_path)
+ if suffix != None:
+ target_path = proposed_path[:extension] + suffix + proposed_path[extension:]
+ index=0
+ while True:
+ r,_=self.db.get_table(FROM="images",WHERE={"path":target_path})
+ if r<=0:
+ return target_path
+ target_path = proposed_path[:extension] + suffix + "-" + str(index) + proposed_path[extension:]
+ index+=1
+
+
+ def delete_file(self, file_name):
+ command = 'rm -f '+file_name
+ print self.name, ': command:', command
+ (_, _, stderr) = self.ssh_conn.exec_command(command)
+ error_msg = stderr.read()
+ if len(error_msg) > 0:
+ raise paramiko.ssh_exception.SSHException("Error deleting file: " + error_msg)
+
+ def copy_file(self, source, destination, perserve_time=True):
+ command = 'cp --no-preserve=mode '
+ if perserve_time: command += '--preserve=timestamps '
+ command += source + ' ' + destination
+ print self.name, ': command:', command
+ (_, _, stderr) = self.ssh_conn.exec_command(command)
+ error_msg = stderr.read()
+ if len(error_msg) > 0:
+ raise paramiko.ssh_exception.SSHException("Error copying image to local host: " + error_msg)
+
+ def copy_remote_file(self, remote_file, use_incremental):
+ ''' Copy a file from the repository to local folder and recursively
+ copy the backing files in case the remote file is incremental
+ Read and/or modified self.localinfo['files'] that contain the
+ unmodified copies of images in the local path
+ params:
+ remote_file: path of remote file
+ use_incremental: None (leave the decision to this function), True, False
+ return:
+ local_file: name of local file
+ qemu_info: dict with quemu information of local file
+ use_incremental_out: True, False; same as use_incremental, but if None a decision is taken
+ '''
+
+ use_incremental_out = use_incremental
+ new_backing_file = None
+ local_file = None
+
+ #in case incremental use is not decided, take the decision depending on the image
+ #avoid the use of incremental if this image is already incremental
+ qemu_remote_info = self.qemu_get_info(remote_file)
+ if use_incremental_out==None:
+ use_incremental_out = not 'backing file' in qemu_remote_info
+ #copy recursivelly the backing files
+ if 'backing file' in qemu_remote_info:
+ new_backing_file, _, _ = self.copy_remote_file(qemu_remote_info['backing file'], True)
+
+ #check if remote file is present locally
+ if use_incremental_out and remote_file in self.localinfo['files']:
+ local_file = self.localinfo['files'][remote_file]
+ local_file_info = self.get_file_info(local_file)
+ remote_file_info = self.get_file_info(remote_file)
+ if local_file_info == None:
+ local_file = None
+ elif local_file_info[4]!=remote_file_info[4] or local_file_info[5]!=remote_file_info[5]:
+ #local copy of file not valid because date or size are different.
+ #TODO DELETE local file if this file is not used by any active virtual machine
+ try:
+ self.delete_file(local_file)
+ del self.localinfo['files'][remote_file]
+ except Exception:
+ pass
+ local_file = None
+ else: #check that the local file has the same backing file, or there are not backing at all
+ qemu_info = self.qemu_get_info(local_file)
+ if new_backing_file != qemu_info.get('backing file'):
+ local_file = None
+
+
+ if local_file == None: #copy the file
+ img_name= remote_file.split('/') [-1]
+ img_local = self.image_path + '/' + img_name
+ local_file = self.get_notused_filename(img_local)
+ self.copy_file(remote_file, local_file, use_incremental_out)
+
+ if use_incremental_out:
+ self.localinfo['files'][remote_file] = local_file
+ if new_backing_file:
+ self.qemu_change_backing(local_file, new_backing_file)
+ qemu_info = self.qemu_get_info(local_file)
+
+ return local_file, qemu_info, use_incremental_out
+
+ def launch_server(self, conn, server, rebuild=False, domain=None):
+ if self.test:
+ time.sleep(random.randint(20,150)) #sleep random timeto be make it a bit more real
+ return 0, 'Success'
+
+ server_id = server['uuid']
+ paused = server.get('paused','no')
+ try:
+ if domain!=None and rebuild==False:
+ domain.resume()
+ #self.server_status[server_id] = 'ACTIVE'
+ return 0, 'Success'
+
+ self.db_lock.acquire()
+ result, server_data = self.db.get_instance(server_id)
+ self.db_lock.release()
+ if result <= 0:
+ print self.name, ": launch_server ERROR getting server from DB",result, server_data
+ return result, server_data
+
+ #0: get image metadata
+ server_metadata = server.get('metadata', {})
+ use_incremental = None
+
+ if "use_incremental" in server_metadata:
+ use_incremental = False if server_metadata["use_incremental"]=="no" else True
+
+ server_host_files = self.localinfo['server_files'].get( server['uuid'], {})
+ if rebuild:
+ #delete previous incremental files
+ for file_ in server_host_files.values():
+ self.delete_file(file_['source file'] )
+ server_host_files={}
+
+ #1: obtain aditional devices (disks)
+ #Put as first device the main disk
+ devices = [ {"type":"disk", "image_id":server['image_id'], "vpci":server_metadata.get('vpci', None) } ]
+ if 'extended' in server_data and server_data['extended']!=None and "devices" in server_data['extended']:
+ devices += server_data['extended']['devices']
+
+ for dev in devices:
+ if dev['image_id'] == None:
+ continue
+
+ self.db_lock.acquire()
+ result, content = self.db.get_table(FROM='images', SELECT=('path','metadata'),WHERE={'uuid':dev['image_id']} )
+ self.db_lock.release()
+ if result <= 0:
+ error_text = "ERROR", result, content, "when getting image", dev['image_id']
+ print self.name, ": launch_server", error_text
+ return -1, error_text
+ if content[0]['metadata'] is not None:
+ dev['metadata'] = json.loads(content[0]['metadata'])
+ else:
+ dev['metadata'] = {}
+
+ if dev['image_id'] in server_host_files:
+ dev['source file'] = server_host_files[ dev['image_id'] ] ['source file'] #local path
+ dev['file format'] = server_host_files[ dev['image_id'] ] ['file format'] # raw or qcow2
+ continue
+
+ #2: copy image to host
+ remote_file = content[0]['path']
+ use_incremental_image = use_incremental
+ if dev['metadata'].get("use_incremental") == "no":
+ use_incremental_image = False
+ local_file, qemu_info, use_incremental_image = self.copy_remote_file(remote_file, use_incremental_image)
+
+ #create incremental image
+ if use_incremental_image:
+ local_file_inc = self.get_notused_filename(local_file, '.inc')
+ command = 'qemu-img create -f qcow2 '+local_file_inc+ ' -o backing_file='+ local_file
+ print 'command:', command
+ (_, _, stderr) = self.ssh_conn.exec_command(command)
+ error_msg = stderr.read()
+ if len(error_msg) > 0:
+ raise paramiko.ssh_exception.SSHException("Error creating incremental file: " + error_msg)
+ local_file = local_file_inc
+ qemu_info = {'file format':'qcow2'}
+
+ server_host_files[ dev['image_id'] ] = {'source file': local_file, 'file format': qemu_info['file format']}
+
+ dev['source file'] = local_file
+ dev['file format'] = qemu_info['file format']
+
+ self.localinfo['server_files'][ server['uuid'] ] = server_host_files
+ self.localinfo_dirty = True
+
+ #3 Create XML
+ result, xml = self.create_xml_server(server_data, devices, server_metadata) #local_file
+ if result <0:
+ print self.name, ": create xml server error:", xml
+ return -2, xml
+ print self.name, ": create xml:", xml
+ atribute = libvirt.VIR_DOMAIN_START_PAUSED if paused == "yes" else 0
+ #4 Start the domain
+ if not rebuild: #ensures that any pending destroying server is done
+ self.server_forceoff(True)
+ #print self.name, ": launching instance" #, xml
+ conn.createXML(xml, atribute)
+ #self.server_status[server_id] = 'PAUSED' if paused == "yes" else 'ACTIVE'
+
+ return 0, 'Success'
+
+ except paramiko.ssh_exception.SSHException as e:
+ text = e.args[0]
+ print self.name, ": launch_server(%s) ssh Exception: %s" %(server_id, text)
+ if "SSH session not active" in text:
+ self.ssh_connect()
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ print self.name, ": launch_server(%s) libvirt Exception: %s" %(server_id, text)
+ except Exception as e:
+ text = str(e)
+ print self.name, ": launch_server(%s) Exception: %s" %(server_id, text)
+ return -1, text
+
+ def update_servers_status(self):
+ # # virDomainState
+ # VIR_DOMAIN_NOSTATE = 0
+ # VIR_DOMAIN_RUNNING = 1
+ # VIR_DOMAIN_BLOCKED = 2
+ # VIR_DOMAIN_PAUSED = 3
+ # VIR_DOMAIN_SHUTDOWN = 4
+ # VIR_DOMAIN_SHUTOFF = 5
+ # VIR_DOMAIN_CRASHED = 6
+ # VIR_DOMAIN_PMSUSPENDED = 7 #TODO suspended
+
+ if self.test or len(self.server_status)==0:
+ return
+
+ try:
+ conn = libvirt.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+ domains= conn.listAllDomains()
+ domain_dict={}
+ for domain in domains:
+ uuid = domain.UUIDString() ;
+ libvirt_status = domain.state()
+ #print libvirt_status
+ if libvirt_status[0] == libvirt.VIR_DOMAIN_RUNNING or libvirt_status[0] == libvirt.VIR_DOMAIN_SHUTDOWN:
+ new_status = "ACTIVE"
+ elif libvirt_status[0] == libvirt.VIR_DOMAIN_PAUSED:
+ new_status = "PAUSED"
+ elif libvirt_status[0] == libvirt.VIR_DOMAIN_SHUTOFF:
+ new_status = "INACTIVE"
+ elif libvirt_status[0] == libvirt.VIR_DOMAIN_CRASHED:
+ new_status = "ERROR"
+ else:
+ new_status = None
+ domain_dict[uuid] = new_status
+ conn.close
+ except libvirt.libvirtError as e:
+ print self.name, ": get_state() Exception '", e.get_error_message()
+ return
+
+ for server_id, current_status in self.server_status.iteritems():
+ new_status = None
+ if server_id in domain_dict:
+ new_status = domain_dict[server_id]
+ else:
+ new_status = "INACTIVE"
+
+ if new_status == None or new_status == current_status:
+ continue
+ if new_status == 'INACTIVE' and current_status == 'ERROR':
+ continue #keep ERROR status, because obviously this machine is not running
+ #change status
+ print self.name, ": server ", server_id, "status change from ", current_status, "to", new_status
+ STATUS={'progress':100, 'status':new_status}
+ if new_status == 'ERROR':
+ STATUS['last_error'] = 'machine has crashed'
+ self.db_lock.acquire()
+ r,_ = self.db.update_rows('instances', STATUS, {'uuid':server_id}, log=False)
+ self.db_lock.release()
+ if r>=0:
+ self.server_status[server_id] = new_status
+
+ def action_on_server(self, req, last_retry=True):
+ '''Perform an action on a req
+ Attributes:
+ req: dictionary that contain:
+ server properties: 'uuid','name','tenant_id','status'
+ action: 'action'
+ host properties: 'user', 'ip_name'
+ return (error, text)
+ 0: No error. VM is updated to new state,
+ -1: Invalid action, as trying to pause a PAUSED VM
+ -2: Error accessing host
+ -3: VM nor present
+ -4: Error at DB access
+ -5: Error while trying to perform action. VM is updated to ERROR
+ '''
+ server_id = req['uuid']
+ conn = None
+ new_status = None
+ old_status = req['status']
+ last_error = None
+
+ if self.test:
+ if 'terminate' in req['action']:
+ new_status = 'deleted'
+ elif 'shutoff' in req['action'] or 'shutdown' in req['action'] or 'forceOff' in req['action']:
+ if req['status']!='ERROR':
+ time.sleep(5)
+ new_status = 'INACTIVE'
+ elif 'start' in req['action'] and req['status']!='ERROR': new_status = 'ACTIVE'
+ elif 'resume' in req['action'] and req['status']!='ERROR' and req['status']!='INACTIVE' : new_status = 'ACTIVE'
+ elif 'pause' in req['action'] and req['status']!='ERROR': new_status = 'PAUSED'
+ elif 'reboot' in req['action'] and req['status']!='ERROR': new_status = 'ACTIVE'
+ elif 'rebuild' in req['action']:
+ time.sleep(random.randint(20,150))
+ new_status = 'ACTIVE'
+ elif 'createImage' in req['action']:
+ time.sleep(5)
+ self.create_image(None, req)
+ else:
+ try:
+ conn = libvirt.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+ try:
+ dom = conn.lookupByUUIDString(server_id)
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ if 'LookupByUUIDString' in text or 'Domain not found' in text or 'No existe un dominio coincidente' in text:
+ dom = None
+ else:
+ print self.name, ": action_on_server(",server_id,") libvirt exception:", text
+ raise e
+
+ if 'forceOff' in req['action']:
+ if dom == None:
+ print self.name, ": action_on_server(",server_id,") domain not running"
+ else:
+ try:
+ print self.name, ": sending DESTROY to server", server_id
+ dom.destroy()
+ except Exception as e:
+ if "domain is not running" not in e.get_error_message():
+ print self.name, ": action_on_server(",server_id,") Exception while sending force off:", e.get_error_message()
+ last_error = 'action_on_server Exception while destroy: ' + e.get_error_message()
+ new_status = 'ERROR'
+
+ elif 'terminate' in req['action']:
+ if dom == None:
+ print self.name, ": action_on_server(",server_id,") domain not running"
+ new_status = 'deleted'
+ else:
+ try:
+ if req['action']['terminate'] == 'force':
+ print self.name, ": sending DESTROY to server", server_id
+ dom.destroy()
+ new_status = 'deleted'
+ else:
+ print self.name, ": sending SHUTDOWN to server", server_id
+ dom.shutdown()
+ self.pending_terminate_server.append( (time.time()+10,server_id) )
+ except Exception as e:
+ print self.name, ": action_on_server(",server_id,") Exception while destroy:", e.get_error_message()
+ last_error = 'action_on_server Exception while destroy: ' + e.get_error_message()
+ new_status = 'ERROR'
+ if "domain is not running" in e.get_error_message():
+ try:
+ dom.undefine()
+ new_status = 'deleted'
+ except Exception:
+ print self.name, ": action_on_server(",server_id,") Exception while undefine:", e.get_error_message()
+ last_error = 'action_on_server Exception2 while undefine:', e.get_error_message()
+ #Exception: 'virDomainDetachDevice() failed'
+ if new_status=='deleted':
+ if server_id in self.server_status:
+ del self.server_status[server_id]
+ if req['uuid'] in self.localinfo['server_files']:
+ for file_ in self.localinfo['server_files'][ req['uuid'] ].values():
+ try:
+ self.delete_file(file_['source file'])
+ except Exception:
+ pass
+ del self.localinfo['server_files'][ req['uuid'] ]
+ self.localinfo_dirty = True
+
+ elif 'shutoff' in req['action'] or 'shutdown' in req['action']:
+ try:
+ if dom == None:
+ print self.name, ": action_on_server(",server_id,") domain not running"
+ else:
+ dom.shutdown()
+# new_status = 'INACTIVE'
+ #TODO: check status for changing at database
+ except Exception as e:
+ new_status = 'ERROR'
+ print self.name, ": action_on_server(",server_id,") Exception while shutdown:", e.get_error_message()
+ last_error = 'action_on_server Exception while shutdown: ' + e.get_error_message()
+
+ elif 'rebuild' in req['action']:
+ if dom != None:
+ dom.destroy()
+ r = self.launch_server(conn, req, True, None)
+ if r[0] <0:
+ new_status = 'ERROR'
+ last_error = r[1]
+ else:
+ new_status = 'ACTIVE'
+ elif 'start' in req['action']:
+ #La instancia está sólo en la base de datos pero no en la libvirt. es necesario crearla
+ rebuild = True if req['action']['start']=='rebuild' else False
+ r = self.launch_server(conn, req, rebuild, dom)
+ if r[0] <0:
+ new_status = 'ERROR'
+ last_error = r[1]
+ else:
+ new_status = 'ACTIVE'
+
+ elif 'resume' in req['action']:
+ try:
+ if dom == None:
+ pass
+ else:
+ dom.resume()
+# new_status = 'ACTIVE'
+ except Exception as e:
+ print self.name, ": action_on_server(",server_id,") Exception while resume:", e.get_error_message()
+
+ elif 'pause' in req['action']:
+ try:
+ if dom == None:
+ pass
+ else:
+ dom.suspend()
+# new_status = 'PAUSED'
+ except Exception as e:
+ print self.name, ": action_on_server(",server_id,") Exception while pause:", e.get_error_message()
+
+ elif 'reboot' in req['action']:
+ try:
+ if dom == None:
+ pass
+ else:
+ dom.reboot()
+ print self.name, ": action_on_server(",server_id,") reboot:"
+ #new_status = 'ACTIVE'
+ except Exception as e:
+ print self.name, ": action_on_server(",server_id,") Exception while reboot:", e.get_error_message()
+ elif 'createImage' in req['action']:
+ self.create_image(dom, req)
+
+
+ conn.close()
+ except libvirt.libvirtError as e:
+ if conn is not None: conn.close
+ text = e.get_error_message()
+ new_status = "ERROR"
+ last_error = text
+ print self.name, ": action_on_server(",server_id,") Exception '", text
+ if 'LookupByUUIDString' in text or 'Domain not found' in text or 'No existe un dominio coincidente' in text:
+ print self.name, ": action_on_server(",server_id,") Exception removed from host"
+ #end of if self.test
+ if new_status == None:
+ return 1
+
+ print self.name, ": action_on_server(",server_id,") new status", new_status, last_error
+ UPDATE = {'progress':100, 'status':new_status}
+
+ if new_status=='ERROR':
+ if not last_retry: #if there will be another retry do not update database
+ return -1
+ elif 'terminate' in req['action']:
+ #PUT a log in the database
+ print self.name, ": PANIC deleting server", server_id, last_error
+ self.db_lock.acquire()
+ self.db.new_row('logs',
+ {'uuid':server_id, 'tenant_id':req['tenant_id'], 'related':'instances','level':'panic',
+ 'description':'PANIC deleting server from host '+self.name+': '+last_error}
+ )
+ self.db_lock.release()
+ if server_id in self.server_status:
+ del self.server_status[server_id]
+ return -1
+ else:
+ UPDATE['last_error'] = last_error
+ if new_status != 'deleted' and (new_status != old_status or new_status == 'ERROR') :
+ self.db_lock.acquire()
+ self.db.update_rows('instances', UPDATE, {'uuid':server_id}, log=True)
+ self.server_status[server_id] = new_status
+ self.db_lock.release()
+ if new_status == 'ERROR':
+ return -1
+ return 1
+
+
+ def restore_iface(self, name, mac, lib_conn=None):
+ ''' make an ifdown, ifup to restore default parameter of na interface
+ Params:
+ mac: mac address of the interface
+ lib_conn: connection to the libvirt, if None a new connection is created
+ Return 0,None if ok, -1,text if fails
+ '''
+ conn=None
+ ret = 0
+ error_text=None
+ if self.test:
+ print self.name, ": restore_iface '%s' %s" % (name, mac)
+ return 0, None
+ try:
+ if not lib_conn:
+ conn = libvirt.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+ else:
+ conn = lib_conn
+
+ #wait to the pending VM deletion
+ #TODO.Revise self.server_forceoff(True)
+
+ iface = conn.interfaceLookupByMACString(mac)
+ iface.destroy()
+ iface.create()
+ print self.name, ": restore_iface '%s' %s" % (name, mac)
+ except libvirt.libvirtError as e:
+ error_text = e.get_error_message()
+ print self.name, ": restore_iface '%s' '%s' libvirt exception: %s" %(name, mac, error_text)
+ ret=-1
+ finally:
+ if lib_conn is None and conn is not None:
+ conn.close
+ return ret, error_text
+
+
+ def create_image(self,dom, req):
+ if self.test:
+ if 'path' in req['action']['createImage']:
+ file_dst = req['action']['createImage']['path']
+ else:
+ createImage=req['action']['createImage']
+ img_name= createImage['source']['path']
+ index=img_name.rfind('/')
+ file_dst = self.get_notused_path(img_name[:index+1] + createImage['name'] + '.qcow2')
+ image_status='ACTIVE'
+ else:
+ for retry in (0,1):
+ try:
+ server_id = req['uuid']
+ createImage=req['action']['createImage']
+ file_orig = self.localinfo['server_files'][server_id] [ createImage['source']['image_id'] ] ['source file']
+ if 'path' in req['action']['createImage']:
+ file_dst = req['action']['createImage']['path']
+ else:
+ img_name= createImage['source']['path']
+ index=img_name.rfind('/')
+ file_dst = self.get_notused_filename(img_name[:index+1] + createImage['name'] + '.qcow2')
+
+ self.copy_file(file_orig, file_dst)
+ qemu_info = self.qemu_get_info(file_orig)
+ if 'backing file' in qemu_info:
+ for k,v in self.localinfo['files'].items():
+ if v==qemu_info['backing file']:
+ self.qemu_change_backing(file_dst, k)
+ break
+ image_status='ACTIVE'
+ break
+ except paramiko.ssh_exception.SSHException as e:
+ image_status='ERROR'
+ error_text = e.args[0]
+ print self.name, "': create_image(",server_id,") ssh Exception:", error_text
+ if "SSH session not active" in error_text and retry==0:
+ self.ssh_connect()
+ except Exception as e:
+ image_status='ERROR'
+ error_text = str(e)
+ print self.name, "': create_image(",server_id,") Exception:", error_text
+
+ #TODO insert a last_error at database
+ self.db_lock.acquire()
+ self.db.update_rows('images', {'status':image_status, 'progress': 100, 'path':file_dst},
+ {'uuid':req['new_image']['uuid']}, log=True)
+ self.db_lock.release()
+
+ def edit_iface(self, port_id, old_net, new_net):
+ #This action imply remove and insert interface to put proper parameters
+ if self.test:
+ time.sleep(1)
+ else:
+ #get iface details
+ self.db_lock.acquire()
+ r,c = self.db.get_table(FROM='ports as p join resources_port as rp on p.uuid=rp.port_id',
+ WHERE={'port_id': port_id})
+ self.db_lock.release()
+ if r<0:
+ print self.name, ": edit_iface(",port_id,") DDBB error:", c
+ return
+ elif r==0:
+ print self.name, ": edit_iface(",port_id,") por not found"
+ return
+ port=c[0]
+ if port["model"]!="VF":
+ print self.name, ": edit_iface(",port_id,") ERROR model must be VF"
+ return
+ #create xml detach file
+ xml=[]
+ self.xml_level = 2
+ xml.append("<interface type='hostdev' managed='yes'>")
+ xml.append(" <mac address='" +port['mac']+ "'/>")
+ xml.append(" <source>"+ self.pci2xml(port['pci'])+"\n </source>")
+ xml.append('</interface>')
+
+
+ try:
+ conn=None
+ conn = libvirt.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+ dom = conn.lookupByUUIDString(port["instance_id"])
+ if old_net:
+ text="\n".join(xml)
+ print self.name, ": edit_iface detaching SRIOV interface", text
+ dom.detachDeviceFlags(text, flags=libvirt.VIR_DOMAIN_AFFECT_LIVE)
+ if new_net:
+ xml[-1] =" <vlan> <tag id='" + str(port['vlan']) + "'/> </vlan>"
+ self.xml_level = 1
+ xml.append(self.pci2xml(port.get('vpci',None)) )
+ xml.append('</interface>')
+ text="\n".join(xml)
+ print self.name, ": edit_iface attaching SRIOV interface", text
+ dom.attachDeviceFlags(text, flags=libvirt.VIR_DOMAIN_AFFECT_LIVE)
+
+ except libvirt.libvirtError as e:
+ text = e.get_error_message()
+ print self.name, ": edit_iface(",port["instance_id"],") libvirt exception:", text
+
+ finally:
+ if conn is not None: conn.close
+
+
+def create_server(server, db, db_lock, only_of_ports):
+ #print "server"
+ #print "server"
+ #print server
+ #print "server"
+ #print "server"
+ #try:
+# host_id = server.get('host_id', None)
+ extended = server.get('extended', None)
+
+# print '----------------------'
+# print json.dumps(extended, indent=4)
+
+ requirements={}
+ requirements['numa']={'memory':0, 'proc_req_type': 'threads', 'proc_req_nb':0, 'port_list':[], 'sriov_list':[]}
+ requirements['ram'] = server['flavor'].get('ram', 0)
+ if requirements['ram']== None:
+ requirements['ram'] = 0
+ requirements['vcpus'] = server['flavor'].get('vcpus', 0)
+ if requirements['vcpus']== None:
+ requirements['vcpus'] = 0
+ #If extended is not defined get requirements from flavor
+ if extended is None:
+ #If extended is defined in flavor convert to dictionary and use it
+ if 'extended' in server['flavor'] and server['flavor']['extended'] != None:
+ json_acceptable_string = server['flavor']['extended'].replace("'", "\"")
+ extended = json.loads(json_acceptable_string)
+ else:
+ extended = None
+ #print json.dumps(extended, indent=4)
+
+ #For simplicity only one numa VM are supported in the initial implementation
+ if extended != None:
+ numas = extended.get('numas', [])
+ if len(numas)>1:
+ return (-2, "Multi-NUMA VMs are not supported yet")
+ #elif len(numas)<1:
+ # return (-1, "At least one numa must be specified")
+
+ #a for loop is used in order to be ready to multi-NUMA VMs
+ request = []
+ for numa in numas:
+ numa_req = {}
+ numa_req['memory'] = numa.get('memory', 0)
+ if 'cores' in numa:
+ numa_req['proc_req_nb'] = numa['cores'] #number of cores or threads to be reserved
+ numa_req['proc_req_type'] = 'cores' #indicates whether cores or threads must be reserved
+ numa_req['proc_req_list'] = numa.get('cores-id', None) #list of ids to be assigned to the cores or threads
+ elif 'paired-threads' in numa:
+ numa_req['proc_req_nb'] = numa['paired-threads']
+ numa_req['proc_req_type'] = 'paired-threads'
+ numa_req['proc_req_list'] = numa.get('paired-threads-id', None)
+ elif 'threads' in numa:
+ numa_req['proc_req_nb'] = numa['threads']
+ numa_req['proc_req_type'] = 'threads'
+ numa_req['proc_req_list'] = numa.get('threads-id', None)
+ else:
+ numa_req['proc_req_nb'] = 0 # by default
+ numa_req['proc_req_type'] = 'threads'
+
+
+
+ #Generate a list of sriov and another for physical interfaces
+ interfaces = numa.get('interfaces', [])
+ sriov_list = []
+ port_list = []
+ for iface in interfaces:
+ iface['bandwidth'] = int(iface['bandwidth'])
+ if iface['dedicated'][:3]=='yes':
+ port_list.append(iface)
+ else:
+ sriov_list.append(iface)
+
+ #Save lists ordered from more restrictive to less bw requirements
+ numa_req['sriov_list'] = sorted(sriov_list, key=lambda k: k['bandwidth'], reverse=True)
+ numa_req['port_list'] = sorted(port_list, key=lambda k: k['bandwidth'], reverse=True)
+
+
+ request.append(numa_req)
+
+ # print "----------\n"+json.dumps(request[0], indent=4)
+ # print '----------\n\n'
+
+ #Search in db for an appropriate numa for each requested numa
+ #at the moment multi-NUMA VMs are not supported
+ if len(request)>0:
+ requirements['numa'].update(request[0])
+ if requirements['numa']['memory']>0:
+ requirements['ram']=0 #By the moment I make incompatible ask for both Huge and non huge pages memory
+ elif requirements['ram']==0:
+ return (-1, "Memory information not set neither at extended field not at ram")
+ if requirements['numa']['proc_req_nb']>0:
+ requirements['vcpus']=0 #By the moment I make incompatible ask for both Isolated and non isolated cpus
+ elif requirements['vcpus']==0:
+ return (-1, "Processor information not set neither at extended field not at vcpus")
+
+
+ db_lock.acquire()
+ result, content = db.get_numas(requirements, server.get('host_id', None), only_of_ports)
+ db_lock.release()
+
+ if result == -1:
+ return (-1, content)
+
+ numa_id = content['numa_id']
+ host_id = content['host_id']
+
+ #obtain threads_id and calculate pinning
+ cpu_pinning = []
+ reserved_threads=[]
+ if requirements['numa']['proc_req_nb']>0:
+ db_lock.acquire()
+ result, content = db.get_table(FROM='resources_core',
+ SELECT=('id','core_id','thread_id'),
+ WHERE={'numa_id':numa_id,'instance_id': None, 'status':'ok'} )
+ db_lock.release()
+ if result <= 0:
+ print content
+ return -1, content
+
+ #convert rows to a dictionary indexed by core_id
+ cores_dict = {}
+ for row in content:
+ if not row['core_id'] in cores_dict:
+ cores_dict[row['core_id']] = []
+ cores_dict[row['core_id']].append([row['thread_id'],row['id']])
+
+ #In case full cores are requested
+ paired = 'N'
+ if requirements['numa']['proc_req_type'] == 'cores':
+ #Get/create the list of the vcpu_ids
+ vcpu_id_list = requirements['numa']['proc_req_list']
+ if vcpu_id_list == None:
+ vcpu_id_list = range(0,int(requirements['numa']['proc_req_nb']))
+
+ for threads in cores_dict.itervalues():
+ #we need full cores
+ if len(threads) != 2:
+ continue
+
+ #set pinning for the first thread
+ cpu_pinning.append( [ vcpu_id_list.pop(0), threads[0][0], threads[0][1] ] )
+
+ #reserve so it is not used the second thread
+ reserved_threads.append(threads[1][1])
+
+ if len(vcpu_id_list) == 0:
+ break
+
+ #In case paired threads are requested
+ elif requirements['numa']['proc_req_type'] == 'paired-threads':
+ paired = 'Y'
+ #Get/create the list of the vcpu_ids
+ if requirements['numa']['proc_req_list'] != None:
+ vcpu_id_list = []
+ for pair in requirements['numa']['proc_req_list']:
+ if len(pair)!=2:
+ return -1, "Field paired-threads-id not properly specified"
+ return
+ vcpu_id_list.append(pair[0])
+ vcpu_id_list.append(pair[1])
+ else:
+ vcpu_id_list = range(0,2*int(requirements['numa']['proc_req_nb']))
+
+ for threads in cores_dict.itervalues():
+ #we need full cores
+ if len(threads) != 2:
+ continue
+ #set pinning for the first thread
+ cpu_pinning.append([vcpu_id_list.pop(0), threads[0][0], threads[0][1]])
+
+ #set pinning for the second thread
+ cpu_pinning.append([vcpu_id_list.pop(0), threads[1][0], threads[1][1]])
+
+ if len(vcpu_id_list) == 0:
+ break
+
+ #In case normal threads are requested
+ elif requirements['numa']['proc_req_type'] == 'threads':
+ #Get/create the list of the vcpu_ids
+ vcpu_id_list = requirements['numa']['proc_req_list']
+ if vcpu_id_list == None:
+ vcpu_id_list = range(0,int(requirements['numa']['proc_req_nb']))
+
+ for threads_index in sorted(cores_dict, key=lambda k: len(cores_dict[k])):
+ threads = cores_dict[threads_index]
+ #set pinning for the first thread
+ cpu_pinning.append([vcpu_id_list.pop(0), threads[0][0], threads[0][1]])
+
+ #if exists, set pinning for the second thread
+ if len(threads) == 2 and len(vcpu_id_list) != 0:
+ cpu_pinning.append([vcpu_id_list.pop(0), threads[1][0], threads[1][1]])
+
+ if len(vcpu_id_list) == 0:
+ break
+
+ #Get the source pci addresses for the selected numa
+ used_sriov_ports = []
+ for port in requirements['numa']['sriov_list']:
+ db_lock.acquire()
+ result, content = db.get_table(FROM='resources_port', SELECT=('id', 'pci', 'mac'),WHERE={'numa_id':numa_id,'root_id': port['port_id'], 'port_id': None, 'Mbps_used': 0} )
+ db_lock.release()
+ if result <= 0:
+ print content
+ return -1, content
+ for row in content:
+ if row['id'] in used_sriov_ports or row['id']==port['port_id']:
+ continue
+ port['pci'] = row['pci']
+ if 'mac_address' not in port:
+ port['mac_address'] = row['mac']
+ del port['mac']
+ port['port_id']=row['id']
+ port['Mbps_used'] = port['bandwidth']
+ used_sriov_ports.append(row['id'])
+ break
+
+ for port in requirements['numa']['port_list']:
+ port['Mbps_used'] = None
+ if port['dedicated'] != "yes:sriov":
+ port['mac_address'] = port['mac']
+ del port['mac']
+ continue
+ db_lock.acquire()
+ result, content = db.get_table(FROM='resources_port', SELECT=('id', 'pci', 'mac', 'Mbps'),WHERE={'numa_id':numa_id,'root_id': port['port_id'], 'port_id': None, 'Mbps_used': 0} )
+ db_lock.release()
+ if result <= 0:
+ print content
+ return -1, content
+ port['Mbps_used'] = content[0]['Mbps']
+ for row in content:
+ if row['id'] in used_sriov_ports or row['id']==port['port_id']:
+ continue
+ port['pci'] = row['pci']
+ if 'mac_address' not in port:
+ port['mac_address'] = row['mac'] # mac cannot be set to passthrough ports
+ del port['mac']
+ port['port_id']=row['id']
+ used_sriov_ports.append(row['id'])
+ break
+
+ # print '2. Physical ports assignation:'+json.dumps(requirements['port_list'], indent=4)
+ # print '2. SR-IOV assignation:'+json.dumps(requirements['sriov_list'], indent=4)
+
+ server['host_id'] = host_id
+
+
+ #Generate dictionary for saving in db the instance resources
+ resources = {}
+ resources['bridged-ifaces'] = []
+
+ numa_dict = {}
+ numa_dict['interfaces'] = []
+
+ numa_dict['interfaces'] += requirements['numa']['port_list']
+ numa_dict['interfaces'] += requirements['numa']['sriov_list']
+
+ #Check bridge information
+ unified_dataplane_iface=[]
+ unified_dataplane_iface += requirements['numa']['port_list']
+ unified_dataplane_iface += requirements['numa']['sriov_list']
+
+ for control_iface in server.get('networks', []):
+ control_iface['net_id']=control_iface.pop('uuid')
+ #Get the brifge name
+ db_lock.acquire()
+ result, content = db.get_table(FROM='nets', SELECT=('name','type', 'vlan'),WHERE={'uuid':control_iface['net_id']} )
+ db_lock.release()
+ if result < 0:
+ pass
+ elif result==0:
+ return -1, "Error at field netwoks: Not found any network wit uuid %s" % control_iface['net_id']
+ else:
+ network=content[0]
+ if control_iface.get("type", 'virtual') == 'virtual':
+ if network['type']!='bridge_data' and network['type']!='bridge_man':
+ return -1, "Error at field netwoks: network uuid %s for control interface is not of type bridge_man or bridge_data" % control_iface['net_id']
+ resources['bridged-ifaces'].append(control_iface)
+ else:
+ if network['type']!='data' and network['type']!='ptp':
+ return -1, "Error at field netwoks: network uuid %s for dataplane interface is not of type data or ptp" % control_iface['net_id']
+ #dataplane interface, look for it in the numa tree and asign this network
+ iface_found=False
+ for dataplane_iface in numa_dict['interfaces']:
+ if dataplane_iface['name'] == control_iface.get("name"):
+ if (dataplane_iface['dedicated'] == "yes" and control_iface["type"] != "PF") or \
+ (dataplane_iface['dedicated'] == "no" and control_iface["type"] != "VF") or \
+ (dataplane_iface['dedicated'] == "yes:sriov" and control_iface["type"] != "VFnotShared") :
+ return -1, "Error at field netwoks: mismatch at interface '%s' from flavor 'dedicated=%s' and networks 'type=%s'" % \
+ (control_iface.get("name"), dataplane_iface['dedicated'], control_iface["type"])
+ dataplane_iface['uuid'] = control_iface['net_id']
+ if dataplane_iface['dedicated'] == "no":
+ dataplane_iface['vlan'] = network['vlan']
+ if dataplane_iface['dedicated'] != "yes" and control_iface.get("mac_address"):
+ dataplane_iface['mac_address'] = control_iface.get("mac_address")
+ if control_iface.get("vpci"):
+ dataplane_iface['vpci'] = control_iface.get("vpci")
+ iface_found=True
+ break
+ if not iface_found:
+ return -1, "Error at field netwoks: interface name %s from network not found at flavor" % control_iface.get("name")
+
+ resources['host_id'] = host_id
+ resources['image_id'] = server['image_id']
+ resources['flavor_id'] = server['flavor_id']
+ resources['tenant_id'] = server['tenant_id']
+ resources['ram'] = requirements['ram']
+ resources['vcpus'] = requirements['vcpus']
+ resources['status'] = 'CREATING'
+
+ if 'description' in server: resources['description'] = server['description']
+ if 'name' in server: resources['name'] = server['name']
+
+ resources['extended'] = {} #optional
+ resources['extended']['numas'] = []
+ numa_dict['numa_id'] = numa_id
+ numa_dict['memory'] = requirements['numa']['memory']
+ numa_dict['cores'] = []
+
+ for core in cpu_pinning:
+ numa_dict['cores'].append({'id': core[2], 'vthread': core[0], 'paired': paired})
+ for core in reserved_threads:
+ numa_dict['cores'].append({'id': core})
+ resources['extended']['numas'].append(numa_dict)
+ if extended!=None and 'devices' in extended: #TODO allow extra devices without numa
+ resources['extended']['devices'] = extended['devices']
+
+
+ print '===================================={'
+ print json.dumps(resources, indent=4)
+ print '====================================}'
+
+ return 0, resources
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is the thread for the http server North API.
+Two thread will be launched, with normal and administrative permissions.
+'''
+
+__author__="Alfonso Tierno"
+__date__ ="$10-jul-2014 12:07:15$"
+
+import bottle
+import yaml
+import json
+import threading
+import datetime
+from utils import RADclass
+from jsonschema import validate as js_v, exceptions as js_e
+import host_thread as ht
+from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \
+ tenant_edit_schema, \
+ flavor_new_schema, flavor_update_schema, \
+ image_new_schema, image_update_schema, \
+ server_new_schema, server_action_schema, network_new_schema, network_update_schema, \
+ port_new_schema, port_update_schema
+
+global my
+global url_base
+global config_dic
+
+url_base="/openvim"
+
+HTTP_Bad_Request = 400
+HTTP_Unauthorized = 401
+HTTP_Not_Found = 404
+HTTP_Forbidden = 403
+HTTP_Method_Not_Allowed = 405
+HTTP_Not_Acceptable = 406
+HTTP_Request_Timeout = 408
+HTTP_Conflict = 409
+HTTP_Service_Unavailable = 503
+HTTP_Internal_Server_Error= 500
+
+
+def check_extended(extended, allow_net_attach=False):
+ '''Makes and extra checking of extended input that cannot be done using jsonschema
+ Attributes:
+ allow_net_attach: for allowing or not the uuid field at interfaces
+ that are allowed for instance, but not for flavors
+ Return: (<0, error_text) if error; (0,None) if not error '''
+ if "numas" not in extended: return 0, None
+ id_s=[]
+ numaid=0
+ for numa in extended["numas"]:
+ nb_formats = 0
+ if "cores" in numa:
+ nb_formats += 1
+ if "cores-id" in numa:
+ if len(numa["cores-id"]) != numa["cores"]:
+ return -HTTP_Bad_Request, "different number of cores-id (%d) than cores (%d) at numa %d" % (len(numa["cores-id"]), numa["cores"],numaid)
+ id_s.extend(numa["cores-id"])
+ if "threads" in numa:
+ nb_formats += 1
+ if "threads-id" in numa:
+ if len(numa["threads-id"]) != numa["threads"]:
+ return -HTTP_Bad_Request, "different number of threads-id (%d) than threads (%d) at numa %d" % (len(numa["threads-id"]), numa["threads"],numaid)
+ id_s.extend(numa["threads-id"])
+ if "paired-threads" in numa:
+ nb_formats += 1
+ if "paired-threads-id" in numa:
+ if len(numa["paired-threads-id"]) != numa["paired-threads"]:
+ return -HTTP_Bad_Request, "different number of paired-threads-id (%d) than paired-threads (%d) at numa %d" % (len(numa["paired-threads-id"]), numa["paired-threads"],numaid)
+ for pair in numa["paired-threads-id"]:
+ if len(pair) != 2:
+ return -HTTP_Bad_Request, "paired-threads-id must contain a list of two elements list at numa %d" % (numaid)
+ id_s.extend(pair)
+ if nb_formats > 1:
+ return -HTTP_Service_Unavailable, "only one of cores, threads, paired-threads are allowed in this version at numa %d" % numaid
+ #check interfaces
+ if "interfaces" in numa:
+ ifaceid=0
+ names=[]
+ vpcis=[]
+ for interface in numa["interfaces"]:
+ if "uuid" in interface and not allow_net_attach:
+ return -HTTP_Bad_Request, "uuid field is not allowed at numa %d interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
+ if "mac_address" in interface and interface["dedicated"]=="yes":
+ return -HTTP_Bad_Request, "mac_address can not be set for dedicated (passthrough) at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
+ if "name" in interface:
+ if interface["name"] in names:
+ return -HTTP_Bad_Request, "name repeated at numa %d, interface %s position %d" % (numaid, interface.get("name",""), ifaceid )
+ names.append(interface["name"])
+ if "vpci" in interface:
+ if interface["vpci"] in vpcis:
+ return -HTTP_Bad_Request, "vpci %s repeated at numa %d, interface %s position %d" % (interface["vpci"], numaid, interface.get("name",""), ifaceid )
+ vpcis.append(interface["vpci"])
+ ifaceid+=1
+ numaid+=1
+ if numaid > 1:
+ return -HTTP_Service_Unavailable, "only one numa can be defined in this version "
+ for a in range(0,len(id_s)):
+ if a not in id_s:
+ return -HTTP_Bad_Request, "core/thread identifiers must start at 0 and gaps are not alloed. Missing id number %d" % a
+
+ return 0, None
+
+#
+# dictionaries that change from HTTP API to database naming
+#
+http2db_host={'id':'uuid'}
+http2db_tenant={'id':'uuid'}
+http2db_flavor={'id':'uuid','imageRef':'image_id'}
+http2db_image={'id':'uuid', 'created':'created_at', 'updated':'modified_at', 'public': 'public'}
+http2db_server={'id':'uuid','hostId':'host_id','flavorRef':'flavor_id','imageRef':'image_id','created':'created_at'}
+http2db_network={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provider'}
+http2db_port={'id':'uuid', 'network_id':'net_id', 'mac_address':'mac', 'device_owner':'type','device_id':'instance_id','binding:switch_port':'switch_port','binding:vlan':'vlan', 'bandwidth':'Mbps'}
+
+def remove_extra_items(data, schema):
+ deleted=[]
+ if type(data) is tuple or type(data) is list:
+ for d in data:
+ a= remove_extra_items(d, schema['items'])
+ if a is not None: deleted.append(a)
+ elif type(data) is dict:
+ for k in data.keys():
+ if 'properties' not in schema or k not in schema['properties'].keys():
+ del data[k]
+ deleted.append(k)
+ else:
+ a = remove_extra_items(data[k], schema['properties'][k])
+ if a is not None: deleted.append({k:a})
+ if len(deleted) == 0: return None
+ elif len(deleted) == 1: return deleted[0]
+ else: return deleted
+
+def delete_nulls(var):
+ if type(var) is dict:
+ for k in var.keys():
+ if var[k] is None: del var[k]
+ elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
+ if delete_nulls(var[k]): del var[k]
+ if len(var) == 0: return True
+ elif type(var) is list or type(var) is tuple:
+ for k in var:
+ if type(k) is dict: delete_nulls(k)
+ if len(var) == 0: return True
+ return False
+
+
+class httpserver(threading.Thread):
+ def __init__(self, db_conn, name="http", host='localhost', port=8080, admin=False, config_=None):
+ '''
+ Creates a new thread to attend the http connections
+ Attributes:
+ db_conn: database connection
+ name: name of this thread
+ host: ip or name where to listen
+ port: port where to listen
+ admin: if this has privileges of administrator or not
+ config_: unless the first thread must be provided. It is a global dictionary where to allocate the self variable
+ '''
+ global url_base
+ global config_dic
+
+ #initialization
+ if config_ is not None:
+ config_dic = config_
+ if 'http_threads' not in config_dic:
+ config_dic['http_threads'] = {}
+ threading.Thread.__init__(self)
+ self.host = host
+ self.port = port
+ self.db = db_conn
+ self.admin = admin
+ if name in config_dic:
+ print "httpserver Warning!!! Onether thread with the same name", name
+ n=0
+ while name+str(n) in config_dic:
+ n +=1
+ name +=str(n)
+ self.name = name
+ self.url_preffix = 'http://' + self.host + ':' + str(self.port) + url_base
+ config_dic['http_threads'][name] = self
+
+ #Ensure that when the main program exits the thread will also exit
+ self.daemon = True
+ self.setDaemon(True)
+
+ def run(self):
+ bottle.run(host=self.host, port=self.port, debug=True) #quiet=True
+
+ def gethost(self, host_id):
+ result, content = self.db.get_host(host_id)
+ if result < 0:
+ print "httpserver.gethost error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ elif result==0:
+ print "httpserver.gethost host '%s' not found" % host_id
+ bottle.abort(HTTP_Not_Found, content)
+ else:
+ data={'host' : content}
+ convert_boolean(content, ('admin_state_up',) )
+ change_keys_http2db(content, http2db_host, reverse=True)
+ print data['host']
+ return format_out(data)
+
+@bottle.route(url_base + '/', method='GET')
+def http_get():
+ print
+ return 'works' #TODO: put links or redirection to /openvim???
+
+#
+# Util funcions
+#
+
+def change_keys_http2db(data, http_db, reverse=False):
+ '''Change keys of dictionary data according to the key_dict values
+ This allow change from http interface names to database names.
+ When reverse is True, the change is otherwise
+ Attributes:
+ data: can be a dictionary or a list
+ http_db: is a dictionary with hhtp names as keys and database names as value
+ reverse: by default change is done from http API to database. If True change is done otherwise
+ Return: None, but data is modified'''
+ if type(data) is tuple or type(data) is list:
+ for d in data:
+ change_keys_http2db(d, http_db, reverse)
+ elif type(data) is dict or type(data) is bottle.FormsDict:
+ if reverse:
+ for k,v in http_db.items():
+ if v in data: data[k]=data.pop(v)
+ else:
+ for k,v in http_db.items():
+ if k in data: data[v]=data.pop(k)
+
+
+
+def format_out(data):
+ '''return string of dictionary data according to requested json, yaml, xml. By default json'''
+ if 'application/yaml' in bottle.request.headers.get('Accept'):
+ bottle.response.content_type='application/yaml'
+ return yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True) #, canonical=True, default_style='"'
+ else: #by default json
+ bottle.response.content_type='application/json'
+ #return data #json no style
+ return json.dumps(data, indent=4) + "\n"
+
+def format_in(schema):
+ try:
+ error_text = "Invalid header format "
+ format_type = bottle.request.headers.get('Content-Type', 'application/json')
+ if 'application/json' in format_type:
+ error_text = "Invalid json format "
+ #Use the json decoder instead of bottle decoder because it informs about the location of error formats with a ValueError exception
+ client_data = json.load(bottle.request.body)
+ #client_data = bottle.request.json()
+ elif 'application/yaml' in format_type:
+ error_text = "Invalid yaml format "
+ client_data = yaml.load(bottle.request.body)
+ elif format_type == 'application/xml':
+ bottle.abort(501, "Content-Type: application/xml not supported yet.")
+ else:
+ print "HTTP HEADERS: " + str(bottle.request.headers.items())
+ bottle.abort(HTTP_Not_Acceptable, 'Content-Type ' + str(format_type) + ' not supported.')
+ return
+ #if client_data == None:
+ # bottle.abort(HTTP_Bad_Request, "Content error, empty")
+ # return
+ #check needed_items
+
+ #print "HTTP input data: ", str(client_data)
+ error_text = "Invalid content "
+ js_v(client_data, schema)
+
+ return client_data
+ except (ValueError, yaml.YAMLError) as exc:
+ error_text += str(exc)
+ print error_text
+ bottle.abort(HTTP_Bad_Request, error_text)
+ except js_e.ValidationError as exc:
+ print "HTTP validate_in error, jsonschema exception ", exc.message, "at", exc.path
+ print " CONTENT: " + str(bottle.request.body.readlines())
+ error_pos = ""
+ if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path)) + "'"
+ bottle.abort(HTTP_Bad_Request, error_text + error_pos+": "+exc.message)
+ #except:
+ # bottle.abort(HTTP_Bad_Request, "Content error: Failed to parse Content-Type", error_pos)
+ # raise
+
+def filter_query_string(qs, http2db, allowed):
+ '''Process query string (qs) checking that contains only valid tokens for avoiding SQL injection
+ Attributes:
+ 'qs': bottle.FormsDict variable to be processed. None or empty is considered valid
+ 'allowed': list of allowed string tokens (API http naming). All the keys of 'qs' must be one of 'allowed'
+ 'http2db': dictionary with change from http API naming (dictionary key) to database naming(dictionary value)
+ Return: A tuple with the (select,where,limit) to be use in a database query. All of then transformed to the database naming
+ select: list of items to retrieve, filtered by query string 'field=token'. If no 'field' is present, allowed list is returned
+ where: dictionary with key, value, taken from the query string token=value. Empty if nothing is provided
+ limit: limit dictated by user with the query string 'limit'. 100 by default
+ abort if not permitted, using bottel.abort
+ '''
+ where={}
+ limit=100
+ select=[]
+ if type(qs) is not bottle.FormsDict:
+ print '!!!!!!!!!!!!!!invalid query string not a dictionary'
+ #bottle.abort(HTTP_Internal_Server_Error, "call programmer")
+ else:
+ for k in qs:
+ if k=='field':
+ select += qs.getall(k)
+ for v in select:
+ if v not in allowed:
+ bottle.abort(HTTP_Bad_Request, "Invalid query string at 'field="+v+"'")
+ elif k=='limit':
+ try:
+ limit=int(qs[k])
+ except:
+ bottle.abort(HTTP_Bad_Request, "Invalid query string at 'limit="+qs[k]+"'")
+ else:
+ if k not in allowed:
+ bottle.abort(HTTP_Bad_Request, "Invalid query string at '"+k+"="+qs[k]+"'")
+ if qs[k]!="null": where[k]=qs[k]
+ else: where[k]=None
+ if len(select)==0: select += allowed
+ #change from http api to database naming
+ for i in range(0,len(select)):
+ k=select[i]
+ if k in http2db:
+ select[i] = http2db[k]
+ change_keys_http2db(where, http2db)
+ #print "filter_query_string", select,where,limit
+
+ return select,where,limit
+
+
+def convert_bandwidth(data, reverse=False):
+ '''Check the field bandwidth recursively and when found, it removes units and convert to number
+ It assumes that bandwidth is well formed
+ Attributes:
+ 'data': dictionary bottle.FormsDict variable to be checked. None or empty is considered valid
+ 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
+ Return:
+ None
+ '''
+ if type(data) is dict:
+ for k in data.keys():
+ if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
+ convert_bandwidth(data[k], reverse)
+ if "bandwidth" in data:
+ try:
+ value=str(data["bandwidth"])
+ if not reverse:
+ pos = value.find("bps")
+ if pos>0:
+ if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
+ elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
+ else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
+ else:
+ value = int(data["bandwidth"])
+ if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
+ else: data["bandwidth"]=str(value) + " Mbps"
+ except:
+ print "convert_bandwidth exception for type", type(data["bandwidth"]), " data", data["bandwidth"]
+ return
+ if type(data) is tuple or type(data) is list:
+ for k in data:
+ if type(k) is dict or type(k) is tuple or type(k) is list:
+ convert_bandwidth(k, reverse)
+
+def convert_boolean(data, items):
+ '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
+ It assumes that bandwidth is well formed
+ Attributes:
+ 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
+ 'items': tuple of keys to convert
+ Return:
+ None
+ '''
+ if type(data) is dict:
+ for k in data.keys():
+ if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
+ convert_boolean(data[k], items)
+ if k in items:
+ if type(data[k]) is str:
+ if data[k]=="false": data[k]=False
+ elif data[k]=="true": data[k]=True
+ if type(data) is tuple or type(data) is list:
+ for k in data:
+ if type(k) is dict or type(k) is tuple or type(k) is list:
+ convert_boolean(k, items)
+
+def convert_datetime2str(var):
+ '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
+ It enters recursively in the dict var finding this kind of variables
+ '''
+ if type(var) is dict:
+ for k,v in var.items():
+ if type(v) is datetime.datetime:
+ var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
+ elif type(v) is dict or type(v) is list or type(v) is tuple:
+ convert_datetime2str(v)
+ if len(var) == 0: return True
+ elif type(var) is list or type(var) is tuple:
+ for v in var:
+ convert_datetime2str(v)
+
+def check_valid_tenant(my, tenant_id):
+ if tenant_id=='any':
+ if not my.admin:
+ return HTTP_Unauthorized, "Needed admin privileges"
+ else:
+ result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id})
+ if result<=0:
+ return HTTP_Not_Found, "tenant '%s' not found" % tenant_id
+ return 0, None
+
+def check_valid_uuid(uuid):
+ id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
+ try:
+ js_v(uuid, id_schema)
+ return True
+ except js_e.ValidationError:
+ return False
+
+@bottle.error(400)
+@bottle.error(401)
+@bottle.error(404)
+@bottle.error(403)
+@bottle.error(405)
+@bottle.error(406)
+@bottle.error(408)
+@bottle.error(409)
+@bottle.error(503)
+@bottle.error(500)
+def error400(error):
+ e={"error":{"code":error.status_code, "type":error.status, "description":error.body}}
+ return format_out(e)
+
+@bottle.hook('after_request')
+def enable_cors():
+ #TODO: Alf: Is it needed??
+ bottle.response.headers['Access-Control-Allow-Origin'] = '*'
+
+#
+# HOSTS
+#
+
+@bottle.route(url_base + '/hosts', method='GET')
+def http_get_hosts():
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_host,
+ ('id','name','description','status','admin_state_up') )
+
+ myself = config_dic['http_threads'][ threading.current_thread().name ]
+ result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_)
+ if result < 0:
+ print "http_get_hosts Error", content
+ bottle.abort(-result, content)
+ else:
+ convert_boolean(content, ('admin_state_up',) )
+ change_keys_http2db(content, http2db_host, reverse=True)
+ for row in content:
+ row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, )
+ data={'hosts' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/hosts/<host_id>', method='GET')
+def http_get_host_id(host_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ return my.gethost(host_id)
+
+@bottle.route(url_base + '/hosts', method='POST')
+def http_post_hosts():
+ '''insert a host into the database. All resources are got and inserted'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check permissions
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+
+ #parse input data
+ http_content = format_in( host_new_schema )
+ r = remove_extra_items(http_content, host_new_schema)
+ if r is not None: print "http_post_host_id: Warning: remove extra items ", r
+ change_keys_http2db(http_content['host'], http2db_host)
+
+ host = http_content['host']
+ warning_text=""
+ if 'host-data' in http_content:
+ host.update(http_content['host-data'])
+ ip_name=http_content['host-data']['ip_name']
+ user=http_content['host-data']['user']
+ password=http_content['host-data'].get('password', None)
+ else:
+ ip_name=host['ip_name']
+ user=host['user']
+ password=host.get('password', None)
+
+ #fill rad info
+ rad = RADclass.RADclass()
+ (return_status, code) = rad.obtain_RAD(user, password, ip_name)
+
+ #return
+ if not return_status:
+ print 'http_post_hosts ERROR obtaining RAD', code
+ bottle.abort(HTTP_Bad_Request, code)
+ return
+ warning_text=code
+ rad_structure = yaml.load(rad.to_text())
+ print 'rad_structure\n---------------------'
+ print json.dumps(rad_structure, indent=4)
+ print '---------------------'
+ #return
+ WHERE_={"family":rad_structure['processor']['family'], 'manufacturer':rad_structure['processor']['manufacturer'], 'version':rad_structure['processor']['version']}
+ result, content = my.db.get_table(FROM='host_ranking',
+ SELECT=('ranking',),
+ WHERE=WHERE_)
+ if result > 0:
+ host['ranking'] = content[0]['ranking']
+ else:
+ #error_text= "Host " + str(WHERE_)+ " not found in ranking table. Not valid for VIM management"
+ #bottle.abort(HTTP_Bad_Request, error_text)
+ #return
+ warning_text += "Host " + str(WHERE_)+ " not found in ranking table. Assuming lowest value 100\n"
+ host['ranking'] = 100 #TODO: as not used in this version, set the lowest value
+
+ features = rad_structure['processor'].get('features', ())
+ host['features'] = ",".join(features)
+ host['numas'] = []
+
+ for node in (rad_structure['resource topology']['nodes'] or {}).itervalues():
+ interfaces= []
+ cores = []
+ eligible_cores=[]
+ count = 0
+ for core in node['cpu']['eligible_cores']:
+ eligible_cores.extend(core)
+ for core in node['cpu']['cores']:
+ for thread_id in core:
+ c={'core_id': count, 'thread_id': thread_id}
+ if thread_id not in eligible_cores: c['status'] = 'noteligible'
+ cores.append(c)
+ count = count+1
+
+ if 'nics' in node:
+ for port_k, port_v in node['nics']['nic 0']['ports'].iteritems():
+ if port_v['virtual']:
+ continue
+ else:
+ sriovs = []
+ for port_k2, port_v2 in node['nics']['nic 0']['ports'].iteritems():
+ if port_v2['virtual'] and port_v2['PF_pci_id']==port_k:
+ sriovs.append({'pci':port_k2, 'mac':port_v2['mac'], 'source_name':port_v2['source_name']})
+ if len(sriovs)>0:
+ #sort sriov according to pci and rename them to the vf number
+ new_sriovs = sorted(sriovs, key=lambda k: k['pci'])
+ index=0
+ for sriov in new_sriovs:
+ sriov['source_name'] = index
+ index += 1
+ interfaces.append ({'pci':str(port_k), 'Mbps': port_v['speed']/1000000, 'sriovs': new_sriovs, 'mac':port_v['mac'], 'source_name':port_v['source_name']})
+ #@TODO LA memoria devuelta por el RAD es incorrecta, almenos para IVY1, NFV100
+ memory=node['memory']['node_size'] / (1024*1024*1024)
+ #memory=get_next_2pow(node['memory']['hugepage_nr'])
+ host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
+ print json.dumps(host, indent=4)
+ #return
+ #
+ #insert in data base
+ result, content = my.db.new_host(host)
+ if result >= 0:
+ if content['admin_state_up']:
+ #create thread
+ host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
+ host_develop_mode = True if config_dic['mode']=='development' else False
+ host_develop_bridge_iface = config_dic.get('development_bridge', None)
+ thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name, db=config_dic['db'], db_lock=config_dic['db_lock'],
+ test=host_test_mode, image_path=config_dic['image_path'],
+ version=config_dic['version'], host_id=content['uuid'],
+ develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface )
+ thread.start()
+ config_dic['host_threads'][ content['uuid'] ] = thread
+
+ #return host data
+ change_keys_http2db(content, http2db_host, reverse=True)
+ if len(warning_text)>0:
+ content["warning"]= warning_text
+ data={'host' : content}
+ return format_out(data)
+ else:
+ bottle.abort(HTTP_Bad_Request, content)
+ return
+
+@bottle.route(url_base + '/hosts/<host_id>', method='PUT')
+def http_put_host_id(host_id):
+ '''modify a host into the database. All resources are got and inserted'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check permissions
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+
+ #parse input data
+ http_content = format_in( host_edit_schema )
+ r = remove_extra_items(http_content, host_edit_schema)
+ if r is not None: print "http_post_host_id: Warning: remove extra items ", r
+ change_keys_http2db(http_content['host'], http2db_host)
+
+ #insert in data base
+ result, content = my.db.edit_host(host_id, http_content['host'])
+ if result >= 0:
+ convert_boolean(content, ('admin_state_up',) )
+ change_keys_http2db(content, http2db_host, reverse=True)
+ data={'host' : content}
+
+ #reload thread
+ config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
+ config_dic['host_threads'][host_id].user = content['user']
+ config_dic['host_threads'][host_id].host = content['ip_name']
+ config_dic['host_threads'][host_id].insert_task("reload")
+
+ #print data
+ return format_out(data)
+ else:
+ bottle.abort(HTTP_Bad_Request, content)
+ return
+
+
+
+@bottle.route(url_base + '/hosts/<host_id>', method='DELETE')
+def http_delete_host_id(host_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check permissions
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ result, content = my.db.delete_row('hosts', host_id)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ #terminate thread
+ if host_id in config_dic['host_threads']:
+ config_dic['host_threads'][host_id].insert_task("exit")
+ #return data
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_host_id error",result, content
+ bottle.abort(-result, content)
+ return
+
+
+
+#
+# TENANTS
+#
+
+@bottle.route(url_base + '/tenants', method='GET')
+def http_get_tenants():
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_tenant,
+ ('id','name','description','enabled') )
+ result, content = my.db.get_table(FROM='tenants', SELECT=select_,WHERE=where_,LIMIT=limit_)
+ if result < 0:
+ print "http_get_tenants Error", content
+ bottle.abort(-result, content)
+ else:
+ change_keys_http2db(content, http2db_tenant, reverse=True)
+ convert_boolean(content, ('enabled',))
+ data={'tenants' : content}
+ #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
+ return format_out(data)
+
+@bottle.route(url_base + '/tenants/<tenant_id>', method='GET')
+def http_get_tenant_id(tenant_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ result, content = my.db.get_table(FROM='tenants', SELECT=('uuid','name','description', 'enabled'),WHERE={'uuid': tenant_id} )
+ if result < 0:
+ print "http_get_tenant_id error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ elif result==0:
+ print "http_get_tenant_id tenant '%s' not found" % tenant_id
+ bottle.abort(HTTP_Not_Found, "tenant %s not found" % tenant_id)
+ else:
+ change_keys_http2db(content, http2db_tenant, reverse=True)
+ convert_boolean(content, ('enabled',))
+ data={'tenant' : content[0]}
+ #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
+ return format_out(data)
+
+
+@bottle.route(url_base + '/tenants', method='POST')
+def http_post_tenants():
+ '''insert a tenant into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #parse input data
+ http_content = format_in( tenant_new_schema )
+ r = remove_extra_items(http_content, tenant_new_schema)
+ if r is not None: print "http_post_tenants: Warning: remove extra items ", r
+ change_keys_http2db(http_content['tenant'], http2db_tenant)
+
+ #insert in data base
+ result, content = my.db.new_tenant(http_content['tenant'])
+
+ if result >= 0:
+ return http_get_tenant_id(content)
+ else:
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/tenants/<tenant_id>', method='PUT')
+def http_put_tenant_id(tenant_id):
+ '''update a tenant into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #parse input data
+ http_content = format_in( tenant_edit_schema )
+ r = remove_extra_items(http_content, tenant_edit_schema)
+ if r is not None: print "http_put_tenant_id: Warning: remove extra items ", r
+ change_keys_http2db(http_content['tenant'], http2db_tenant)
+
+ #insert in data base
+ result, content = my.db.update_rows('tenants', http_content['tenant'], WHERE={'uuid': tenant_id}, log=True )
+ if result >= 0:
+ return http_get_tenant_id(tenant_id)
+ else:
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/tenants/<tenant_id>', method='DELETE')
+def http_delete_tenant_id(tenant_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check permissions
+ r, tenants_flavors = my.db.get_table(FROM='tenants_flavors', SELECT=('flavor_id','tenant_id'), WHERE={'tenant_id': tenant_id})
+ if r<=0:
+ tenants_flavors=()
+ r, tenants_images = my.db.get_table(FROM='tenants_images', SELECT=('image_id','tenant_id'), WHERE={'tenant_id': tenant_id})
+ if r<=0:
+ tenants_images=()
+ result, content = my.db.delete_row('tenants', tenant_id)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ print "alf", tenants_flavors, tenants_images
+ for flavor in tenants_flavors:
+ my.db.delete_row_by_key("flavors", "uuid", flavor['flavor_id'])
+ for image in tenants_images:
+ my.db.delete_row_by_key("images", "uuid", image['image_id'])
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_tenant_id error",result, content
+ bottle.abort(-result, content)
+ return
+
+#
+# FLAVORS
+#
+
+@bottle.route(url_base + '/<tenant_id>/flavors', method='GET')
+def http_get_flavors(tenant_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
+ ('id','name','description','public') )
+ if tenant_id=='any':
+ from_ ='flavors'
+ else:
+ from_ ='tenants_flavors inner join flavors on tenants_flavors.flavor_id=flavors.uuid'
+ where_['tenant_id'] = tenant_id
+ result, content = my.db.get_table(FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_)
+ if result < 0:
+ print "http_get_flavors Error", content
+ bottle.abort(-result, content)
+ else:
+ change_keys_http2db(content, http2db_flavor, reverse=True)
+ for row in content:
+ row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(row['id']) ) ), 'rel':'bookmark' } ]
+ data={'flavors' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='GET')
+def http_get_flavor_id(tenant_id, flavor_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_flavor,
+ ('id','name','description','ram', 'vcpus', 'extended', 'disk', 'public') )
+ if tenant_id=='any':
+ from_ ='flavors'
+ else:
+ from_ ='tenants_flavors as tf inner join flavors as f on tf.flavor_id=f.uuid'
+ where_['tenant_id'] = tenant_id
+ where_['uuid'] = flavor_id
+ result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
+
+ if result < 0:
+ print "http_get_flavor_id error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ elif result==0:
+ print "http_get_flavors_id flavor '%s' not found" % str(flavor_id)
+ bottle.abort(HTTP_Not_Found, 'flavor %s not found' % flavor_id)
+ else:
+ change_keys_http2db(content, http2db_flavor, reverse=True)
+ if 'extended' in content[0] and content[0]['extended'] is not None:
+ extended = json.loads(content[0]['extended'])
+ if 'devices' in extended:
+ change_keys_http2db(extended['devices'], http2db_flavor, reverse=True)
+ content[0]['extended']=extended
+ convert_bandwidth(content[0], reverse=True)
+ content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'flavors', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
+ data={'flavor' : content[0]}
+ #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
+ return format_out(data)
+
+
+@bottle.route(url_base + '/<tenant_id>/flavors', method='POST')
+def http_post_flavors(tenant_id):
+ '''insert a flavor into the database, and attach to tenant.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ http_content = format_in( flavor_new_schema )
+ r = remove_extra_items(http_content, flavor_new_schema)
+ if r is not None: print "http_post_flavors: Warning: remove extra items ", r
+ change_keys_http2db(http_content['flavor'], http2db_flavor)
+ extended_dict = http_content['flavor'].pop('extended', None)
+ if extended_dict is not None:
+ result, content = check_extended(extended_dict)
+ if result<0:
+ print "http_post_flavors wrong input extended error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+ convert_bandwidth(extended_dict)
+ if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
+ http_content['flavor']['extended'] = json.dumps(extended_dict)
+ #insert in data base
+ result, content = my.db.new_flavor(http_content['flavor'], tenant_id)
+ if result >= 0:
+ return http_get_flavor_id(tenant_id, content)
+ else:
+ print "http_psot_flavors error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='DELETE')
+def http_delete_flavor_id(tenant_id, flavor_id):
+ '''Deletes the flavor_id of a tenant. IT removes from tenants_flavors table.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+ result, content = my.db.delete_image_flavor('flavor', flavor_id, tenant_id)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_flavor_id error",result, content
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>/<action>', method='POST')
+def http_attach_detach_flavors(tenant_id, flavor_id, action):
+ '''attach/detach an existing flavor in this tenant. That is insert/remove at tenants_flavors table.'''
+ #TODO alf: not tested at all!!!
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ if tenant_id=='any':
+ bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
+ #check valid action
+ if action!='attach' and action != 'detach':
+ bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
+ return
+
+ #Ensure that flavor exist
+ from_ ='tenants_flavors as tf right join flavors as f on tf.flavor_id=f.uuid'
+ where_={'uuid': flavor_id}
+ result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
+ if result==0:
+ if action=='attach':
+ text_error="Flavor '%s' not found" % flavor_id
+ else:
+ text_error="Flavor '%s' not found for tenant '%s'" % (flavor_id, tenant_id)
+ bottle.abort(HTTP_Not_Found, text_error)
+ return
+ elif result>0:
+ flavor=content[0]
+ if action=='attach':
+ if flavor['tenant_id']!=None:
+ bottle.abort(HTTP_Conflict, "Flavor '%s' already attached to tenant '%s'" % (flavor_id, tenant_id))
+ if flavor['public']=='no' and not my.admin:
+ #allow only attaching public flavors
+ bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private flavor")
+ return
+ #insert in data base
+ result, content = my.db.new_row('tenants_flavors', {'flavor_id':flavor_id, 'tenant_id': tenant_id})
+ if result >= 0:
+ return http_get_flavor_id(tenant_id, flavor_id)
+ else: #detach
+ if flavor['tenant_id']==None:
+ bottle.abort(HTTP_Not_Found, "Flavor '%s' not attached to tenant '%s'" % (flavor_id, tenant_id))
+ result, content = my.db.delete_row_by_dict(FROM='tenants_flavors', WHERE={'flavor_id':flavor_id, 'tenant_id':tenant_id})
+ if result>=0:
+ if flavor['public']=='no':
+ #try to delete the flavor completely to avoid orphan flavors, IGNORE error
+ my.db.delete_row_by_dict(FROM='flavors', WHERE={'uuid':flavor_id})
+ data={'result' : "flavor detached"}
+ return format_out(data)
+
+ #if get here is because an error
+ print "http_attach_detach_flavors error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/flavors/<flavor_id>', method='PUT')
+def http_put_flavor_id(tenant_id, flavor_id):
+ '''update a flavor_id into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #parse input data
+ http_content = format_in( flavor_update_schema )
+ r = remove_extra_items(http_content, flavor_update_schema)
+ if r is not None: print "http_put_flavor_id: Warning: remove extra items ", r
+ change_keys_http2db(http_content['flavor'], http2db_flavor)
+ extended_dict = http_content['flavor'].pop('extended', None)
+ if extended_dict is not None:
+ result, content = check_extended(extended_dict)
+ if result<0:
+ print "http_put_flavor_id wrong input extended error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+ convert_bandwidth(extended_dict)
+ if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_flavor)
+ http_content['flavor']['extended'] = json.dumps(extended_dict)
+ #Ensure that flavor exist
+ where_={'uuid': flavor_id}
+ if tenant_id=='any':
+ from_ ='flavors'
+ else:
+ from_ ='tenants_flavors as ti inner join flavors as i on ti.flavor_id=i.uuid'
+ where_['tenant_id'] = tenant_id
+ result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
+ if result==0:
+ text_error="Flavor '%s' not found" % flavor_id
+ if tenant_id!='any':
+ text_error +=" for tenant '%s'" % flavor_id
+ bottle.abort(HTTP_Not_Found, text_error)
+ return
+ elif result>0:
+ if content[0]['public']=='yes' and not my.admin:
+ #allow only modifications over private flavors
+ bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public flavor")
+ return
+ #insert in data base
+ result, content = my.db.update_rows('flavors', http_content['flavor'], {'uuid': flavor_id})
+
+ if result < 0:
+ print "http_put_flavor_id error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+ else:
+ return http_get_flavor_id(tenant_id, flavor_id)
+
+
+
+#
+# IMAGES
+#
+
+@bottle.route(url_base + '/<tenant_id>/images', method='GET')
+def http_get_images(tenant_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
+ ('id','name','description','path','public') )
+ if tenant_id=='any':
+ from_ ='images'
+ else:
+ from_ ='tenants_images inner join images on tenants_images.image_id=images.uuid'
+ where_['tenant_id'] = tenant_id
+ result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
+ if result < 0:
+ print "http_get_images Error", content
+ bottle.abort(-result, content)
+ else:
+ change_keys_http2db(content, http2db_image, reverse=True)
+ #for row in content: row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(row['id']) ) ), 'rel':'bookmark' } ]
+ data={'images' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='GET')
+def http_get_image_id(tenant_id, image_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_image,
+ ('id','name','description','progress', 'status','path', 'created', 'updated','public') )
+ if tenant_id=='any':
+ from_ ='images'
+ else:
+ from_ ='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
+ where_['tenant_id'] = tenant_id
+ where_['uuid'] = image_id
+ result, content = my.db.get_table(SELECT=select_, FROM=from_, WHERE=where_, LIMIT=limit_)
+
+ if result < 0:
+ print "http_get_images error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ elif result==0:
+ print "http_get_images image '%s' not found" % str(image_id)
+ bottle.abort(HTTP_Not_Found, 'image %s not found' % image_id)
+ else:
+ convert_datetime2str(content)
+ change_keys_http2db(content, http2db_image, reverse=True)
+ if 'metadata' in content[0] and content[0]['metadata'] is not None:
+ metadata = json.loads(content[0]['metadata'])
+ content[0]['metadata']=metadata
+ content[0]['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'images', str(content[0]['id']) ) ), 'rel':'bookmark' } ]
+ data={'image' : content[0]}
+ #data['tenants_links'] = dict([('tenant', row['id']) for row in content])
+ return format_out(data)
+
+@bottle.route(url_base + '/<tenant_id>/images', method='POST')
+def http_post_images(tenant_id):
+ '''insert a image into the database, and attach to tenant.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ http_content = format_in(image_new_schema)
+ r = remove_extra_items(http_content, image_new_schema)
+ if r is not None: print "http_post_images: Warning: remove extra items ", r
+ change_keys_http2db(http_content['image'], http2db_image)
+ metadata_dict = http_content['image'].pop('metadata', None)
+ if metadata_dict is not None:
+ http_content['image']['metadata'] = json.dumps(metadata_dict)
+ #insert in data base
+ result, content = my.db.new_image(http_content['image'], tenant_id)
+ if result >= 0:
+ return http_get_image_id(tenant_id, content)
+ else:
+ print "http_post_images error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='DELETE')
+def http_delete_image_id(tenant_id, image_id):
+ '''Deletes the image_id of a tenant. IT removes from tenants_images table.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ result, content = my.db.delete_image_flavor('image', image_id, tenant_id)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_image_id error",result, content
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/images/<image_id>/<action>', method='POST')
+def http_attach_detach_images(tenant_id, image_id, action):
+ '''attach/detach an existing image in this tenant. That is insert/remove at tenants_images table.'''
+ #TODO alf: not tested at all!!!
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ if tenant_id=='any':
+ bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
+ #check valid action
+ if action!='attach' and action != 'detach':
+ bottle.abort(HTTP_Method_Not_Allowed, "actions can be attach or detach")
+ return
+
+ #Ensure that image exist
+ from_ ='tenants_images as ti right join images as i on ti.image_id=i.uuid'
+ where_={'uuid': image_id}
+ result, content = my.db.get_table(SELECT=('public','tenant_id'), FROM=from_, WHERE=where_)
+ if result==0:
+ if action=='attach':
+ text_error="Image '%s' not found" % image_id
+ else:
+ text_error="Image '%s' not found for tenant '%s'" % (image_id, tenant_id)
+ bottle.abort(HTTP_Not_Found, text_error)
+ return
+ elif result>0:
+ image=content[0]
+ if action=='attach':
+ if image['tenant_id']!=None:
+ bottle.abort(HTTP_Conflict, "Image '%s' already attached to tenant '%s'" % (image_id, tenant_id))
+ if image['public']=='no' and not my.admin:
+ #allow only attaching public images
+ bottle.abort(HTTP_Unauthorized, "Needed admin rights to attach a private image")
+ return
+ #insert in data base
+ result, content = my.db.new_row('tenants_images', {'image_id':image_id, 'tenant_id': tenant_id})
+ if result >= 0:
+ return http_get_image_id(tenant_id, image_id)
+ else: #detach
+ if image['tenant_id']==None:
+ bottle.abort(HTTP_Not_Found, "Image '%s' not attached to tenant '%s'" % (image_id, tenant_id))
+ result, content = my.db.delete_row_by_dict(FROM='tenants_images', WHERE={'image_id':image_id, 'tenant_id':tenant_id})
+ if result>=0:
+ if image['public']=='no':
+ #try to delete the image completely to avoid orphan images, IGNORE error
+ my.db.delete_row_by_dict(FROM='images', WHERE={'uuid':image_id})
+ data={'result' : "image detached"}
+ return format_out(data)
+
+ #if get here is because an error
+ print "http_attach_detach_images error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/images/<image_id>', method='PUT')
+def http_put_image_id(tenant_id, image_id):
+ '''update a image_id into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ #parse input data
+ http_content = format_in( image_update_schema )
+ r = remove_extra_items(http_content, image_update_schema)
+ if r is not None: print "http_put_image_id: Warning: remove extra items ", r
+ change_keys_http2db(http_content['image'], http2db_image)
+ metadata_dict = http_content['image'].pop('metadata', None)
+ if metadata_dict is not None:
+ http_content['image']['metadata'] = json.dumps(metadata_dict)
+ #Ensure that image exist
+ where_={'uuid': image_id}
+ if tenant_id=='any':
+ from_ ='images'
+ else:
+ from_ ='tenants_images as ti inner join images as i on ti.image_id=i.uuid'
+ where_['tenant_id'] = tenant_id
+ result, content = my.db.get_table(SELECT=('public',), FROM=from_, WHERE=where_)
+ if result==0:
+ text_error="Image '%s' not found" % image_id
+ if tenant_id!='any':
+ text_error +=" for tenant '%s'" % image_id
+ bottle.abort(HTTP_Not_Found, text_error)
+ return
+ elif result>0:
+ if content[0]['public']=='yes' and not my.admin:
+ #allow only modifications over private images
+ bottle.abort(HTTP_Unauthorized, "Needed admin rights to edit a public image")
+ return
+ #insert in data base
+ result, content = my.db.update_rows('images', http_content['image'], {'uuid': image_id})
+
+ if result < 0:
+ print "http_put_image_id error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+ else:
+ return http_get_image_id(tenant_id, image_id)
+
+
+#
+# SERVERS
+#
+
+@bottle.route(url_base + '/<tenant_id>/servers', method='GET')
+def http_get_servers(tenant_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_server,
+ ('id','name','description','hostId','imageRef','flavorRef','status', 'tenant_id') )
+ if tenant_id!='any':
+ where_['tenant_id'] = tenant_id
+ result, content = my.db.get_table(SELECT=select_, FROM='instances', WHERE=where_, LIMIT=limit_)
+ if result < 0:
+ print "http_get_servers Error", content
+ bottle.abort(-result, content)
+ else:
+ change_keys_http2db(content, http2db_server, reverse=True)
+ for row in content:
+ tenant_id = row.pop('tenant_id')
+ row['links']=[ {'href': "/".join( (my.url_preffix, tenant_id, 'servers', str(row['id']) ) ), 'rel':'bookmark' } ]
+ data={'servers' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='GET')
+def http_get_server_id(tenant_id, server_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+ #obtain data
+ result, content = my.db.get_instance(server_id)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ #change image/flavor-id to id and link
+ convert_bandwidth(content, reverse=True)
+ convert_datetime2str(content)
+ if content["ram"]==0 : del content["ram"]
+ if content["vcpus"]==0 : del content["vcpus"]
+ if 'flavor_id' in content:
+ if content['flavor_id'] is not None:
+ content['flavor'] = {'id':content['flavor_id'],
+ 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'flavors', str(content['flavor_id']) ) ), 'rel':'bookmark'}]
+ }
+ del content['flavor_id']
+ if 'image_id' in content:
+ if content['image_id'] is not None:
+ content['image'] = {'id':content['image_id'],
+ 'links':[{'href': "/".join( (my.url_preffix, content['tenant_id'], 'images', str(content['image_id']) ) ), 'rel':'bookmark'}]
+ }
+ del content['image_id']
+ change_keys_http2db(content, http2db_server, reverse=True)
+ if 'extended' in content:
+ if 'devices' in content['extended']: change_keys_http2db(content['extended']['devices'], http2db_server, reverse=True)
+
+ data={'server' : content}
+ return format_out(data)
+ else:
+ bottle.abort(-result, content)
+ return
+
+@bottle.route(url_base + '/<tenant_id>/servers', method='POST')
+def http_post_server_id(tenant_id):
+ '''deploys a new server'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+ if tenant_id=='any':
+ bottle.abort(HTTP_Bad_Request, "Invalid tenant 'any' with this command")
+ #chek input
+ http_content = format_in( server_new_schema )
+ r = remove_extra_items(http_content, server_new_schema)
+ if r is not None: print "http_post_serves: Warning: remove extra items ", r
+ change_keys_http2db(http_content['server'], http2db_server)
+ extended_dict = http_content['server'].get('extended', None)
+ if extended_dict is not None:
+ result, content = check_extended(extended_dict, True)
+ if result<0:
+ print "http_post_servers wrong input extended error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+ convert_bandwidth(extended_dict)
+ if 'devices' in extended_dict: change_keys_http2db(extended_dict['devices'], http2db_server)
+
+ server = http_content['server']
+ server_start = server.get('start', 'yes')
+ server['tenant_id'] = tenant_id
+ #check flavor valid and take info
+ result, content = my.db.get_table(FROM='tenants_flavors as tf join flavors as f on tf.flavor_id=f.uuid',
+ SELECT=('ram','vcpus','extended'), WHERE={'uuid':server['flavor_id'], 'tenant_id':tenant_id})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'flavor_id %s not found' % server['flavor_id'])
+ return
+ server['flavor']=content[0]
+ #check image valid and take info
+ result, content = my.db.get_table(FROM='tenants_images as ti join images as i on ti.image_id=i.uuid',
+ SELECT=('path','metadata'), WHERE={'uuid':server['image_id'], 'tenant_id':tenant_id, "status":"ACTIVE"})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % server['image_id'])
+ return
+ server['image']=content[0]
+ if "hosts_id" in server:
+ result, content = my.db.get_table(FROM='hosts', SELECT=('uuid',), WHERE={'uuid': server['host_id']})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'hostId %s not found' % server['host_id'])
+ return
+ #print json.dumps(server, indent=4)
+
+ result, content = ht.create_server(server, config_dic['db'], config_dic['db_lock'], config_dic['mode']=='normal')
+
+ if result >= 0:
+ #Insert instance to database
+ nets=[]
+ print
+ print "inserting at DB"
+ print
+ if server_start == 'no':
+ content['status'] = 'INACTIVE'
+ ports_to_free=[]
+ new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
+ if new_instance_result < 0:
+ print "Error http_post_servers() :", new_instance_result, new_instance
+ bottle.abort(-new_instance_result, new_instance)
+ return
+ print
+ print "inserted at DB"
+ print
+ for port in ports_to_free:
+ r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
+ if r < 0:
+ print ' http_post_servers ERROR RESTORE IFACE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
+ #updata nets
+ for net in nets:
+ r,c = config_dic['of_thread'].insert_task("update-net", net)
+ if r < 0:
+ print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
+
+
+
+ #look for dhcp ip address
+ r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": new_instance})
+ if r2 >0 and config_dic.get("dhcp_server"):
+ for iface in c2:
+ if iface["net_id"] in config_dic["dhcp_nets"]:
+ #print "dhcp insert add task"
+ r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"])
+ if r < 0:
+ print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
+
+ #Start server
+
+ server['uuid'] = new_instance
+ #server_start = server.get('start', 'yes')
+ if server_start != 'no':
+ server['paused'] = True if server_start == 'paused' else False
+ server['action'] = {"start":None}
+ server['status'] = "CREATING"
+ #Program task
+ r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
+ if r<0:
+ my.db.update_rows('instances', {'status':"ERROR"}, {'uuid':server['uuid'], 'last_error':c}, log=True)
+
+ return http_get_server_id(tenant_id, new_instance)
+ else:
+ bottle.abort(HTTP_Bad_Request, content)
+ return
+
+def http_server_action(server_id, tenant_id, action):
+ '''Perform actions over a server as resume, reboot, terminate, ...'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ server={"uuid": server_id, "action":action}
+ where={'uuid': server_id}
+ if tenant_id!='any':
+ where['tenant_id']= tenant_id
+ result, content = my.db.get_table(FROM='instances', WHERE=where)
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, "server %s not found" % server_id)
+ return
+ if result < 0:
+ print "http_post_server_action error getting data %d %s" % (result, content)
+ bottle.abort(HTTP_Internal_Server_Error, content)
+ return
+ server.update(content[0])
+ tenant_id = server["tenant_id"]
+
+ #TODO check a right content
+ new_status = None
+ if 'terminate' in action:
+ new_status='DELETING'
+ elif server['status'] == 'ERROR': #or server['status'] == 'CREATING':
+ if 'terminate' not in action and 'rebuild' not in action:
+ bottle.abort(HTTP_Method_Not_Allowed, "Server is in ERROR status, must be rebuit or deleted ")
+ return
+# elif server['status'] == 'INACTIVE':
+# if 'start' not in action and 'createImage' not in action:
+# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'INACTIVE' status is 'start'")
+# return
+# if 'start' in action:
+# new_status='CREATING'
+# server['paused']='no'
+# elif server['status'] == 'PAUSED':
+# if 'resume' not in action:
+# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'PAUSED' status is 'resume'")
+# return
+# elif server['status'] == 'ACTIVE':
+# if 'pause' not in action and 'reboot'not in action and 'shutoff'not in action:
+# bottle.abort(HTTP_Method_Not_Allowed, "The only possible action over an instance in 'ACTIVE' status is 'pause','reboot' or 'shutoff'")
+# return
+
+ if 'start' in action or 'createImage' in action or 'rebuild' in action:
+ #check image valid and take info
+ image_id = server['image_id']
+ if 'createImage' in action:
+ if 'imageRef' in action['createImage']:
+ image_id = action['createImage']['imageRef']
+ elif 'disk' in action['createImage']:
+ result, content = my.db.get_table(FROM='instance_devices',
+ SELECT=('image_id','dev'), WHERE={'instance_id':server['uuid'],"type":"disk"})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'disk not found for server')
+ return
+ elif result>1:
+ disk_id=None
+ if action['createImage']['imageRef']['disk'] != None:
+ for disk in content:
+ if disk['dev'] == action['createImage']['imageRef']['disk']:
+ disk_id = disk['image_id']
+ break
+ if disk_id == None:
+ bottle.abort(HTTP_Not_Found, 'disk %s not found for server' % action['createImage']['imageRef']['disk'])
+ return
+ else:
+ bottle.abort(HTTP_Not_Found, 'more than one disk found for server' )
+ return
+ image_id = disk_id
+ else: #result==1
+ image_id = content[0]['image_id']
+
+ result, content = my.db.get_table(FROM='tenants_images as ti join images as i on ti.image_id=i.uuid',
+ SELECT=('path','metadata'), WHERE={'uuid':image_id, 'tenant_id':tenant_id, "status":"ACTIVE"})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'image_id %s not found or not ACTIVE' % image_id)
+ return
+ if content[0]['metadata'] is not None:
+ try:
+ metadata = json.loads(content[0]['metadata'])
+ except:
+ return -HTTP_Internal_Server_Error, "Can not decode image metadata"
+ content[0]['metadata']=metadata
+ else:
+ content[0]['metadata'] = {}
+ server['image']=content[0]
+ if 'createImage' in action:
+ action['createImage']['source'] = {'image_id': image_id, 'path': content[0]['path']}
+ if 'createImage' in action:
+ #Create an entry in Database for the new image
+ new_image={'status':'BUILD', 'progress': 0 }
+ new_image_metadata=content[0]
+ if 'metadata' in server['image'] and server['image']['metadata'] != None:
+ new_image_metadata.update(server['image']['metadata'])
+ new_image_metadata = {"use_incremental":"no"}
+ if 'metadata' in action['createImage']:
+ new_image_metadata.update(action['createImage']['metadata'])
+ new_image['metadata'] = json.dumps(new_image_metadata)
+ new_image['name'] = action['createImage'].get('name', None)
+ new_image['description'] = action['createImage'].get('description', None)
+ new_image['uuid']=my.db.new_uuid()
+ if 'path' in action['createImage']:
+ new_image['path'] = action['createImage']['path']
+ else:
+ new_image['path']="/provisional/path/" + new_image['uuid']
+ result, image_uuid = my.db.new_image(new_image, tenant_id)
+ if result<=0:
+ bottle.abort(HTTP_Bad_Request, 'Error: ' + image_uuid)
+ return
+ server['new_image'] = new_image
+
+
+ #Program task
+ r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'instance',server )
+ if r<0:
+ print "Task queue full at host ", server['host_id']
+ bottle.abort(HTTP_Request_Timeout, c)
+ if 'createImage' in action and result >= 0:
+ return http_get_image_id(tenant_id, image_uuid)
+
+ #Update DB only for CREATING or DELETING status
+ data={'result' : 'in process'}
+ if new_status != None and new_status == 'DELETING':
+ nets=[]
+ ports_to_free=[]
+ #look for dhcp ip address
+ r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
+ r,c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, "requested by http")
+ for port in ports_to_free:
+ r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
+ if r1 < 0:
+ print ' http_post_server_action error at server deletion ERROR resore-iface !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
+ data={'result' : 'deleting in process, but ifaces cannot be restored!!!!!'}
+ for net in nets:
+ r1,c1 = config_dic['of_thread'].insert_task("update-net", net)
+ if r1 < 0:
+ print ' http_post_server_action error at server deletion ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c1
+ data={'result' : 'deleting in process, but openflow rules cannot be deleted!!!!!'}
+ #look for dhcp ip address
+ if r2 >0 and config_dic.get("dhcp_server"):
+ for iface in c2:
+ if iface["net_id"] in config_dic["dhcp_nets"]:
+ r,c = config_dic['dhcp_thread'].insert_task("del", iface["mac"])
+ #print "dhcp insert del task"
+ if r < 0:
+ print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c
+
+ return format_out(data)
+
+
+
+@bottle.route(url_base + '/<tenant_id>/servers/<server_id>', method='DELETE')
+def http_delete_server_id(tenant_id, server_id):
+ '''delete a server'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+
+ return http_server_action(server_id, tenant_id, {"terminate":None} )
+
+
+@bottle.route(url_base + '/<tenant_id>/servers/<server_id>/action', method='POST')
+def http_post_server_action(tenant_id, server_id):
+ '''take an action over a server'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #check valid tenant_id
+ result,content = check_valid_tenant(my, tenant_id)
+ if result != 0:
+ bottle.abort(result, content)
+ return
+ http_content = format_in( server_action_schema )
+ #r = remove_extra_items(http_content, server_action_schema)
+ #if r is not None: print "http_post_server_action: Warning: remove extra items ", r
+
+ return http_server_action(server_id, tenant_id, http_content)
+
+#
+# NETWORKS
+#
+
+
+@bottle.route(url_base + '/networks', method='GET')
+def http_get_networks():
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #obtain data
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_network,
+ ('id','name','tenant_id','type',
+ 'shared','provider:vlan','status','last_error','admin_state_up','provider:physical') )
+ #TODO temporally remove tenant_id
+ if "tenant_id" in where_:
+ del where_["tenant_id"]
+ result, content = my.db.get_table(SELECT=select_, FROM='nets', WHERE=where_, LIMIT=limit_)
+ if result < 0:
+ print "http_get_networks error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ else:
+ convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp') )
+ delete_nulls(content)
+ change_keys_http2db(content, http2db_network, reverse=True)
+ data={'networks' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/networks/<network_id>', method='GET')
+def http_get_network_id(network_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #obtain data
+ where_ = bottle.request.query
+ where_['uuid'] = network_id
+ result, content = my.db.get_table(FROM='nets', WHERE=where_, LIMIT=100)
+
+ if result < 0:
+ print "http_get_networks_id error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ elif result==0:
+ print "http_get_networks_id network '%s' not found" % network_id
+ bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
+ else:
+ convert_boolean(content, ('shared', 'admin_state_up', 'enale_dhcp') )
+ change_keys_http2db(content, http2db_network, reverse=True)
+ #get ports
+ result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
+ WHERE={'net_id': network_id}, LIMIT=100)
+ if len(ports) > 0:
+ content[0]['ports'] = ports
+ delete_nulls(content[0])
+ data={'network' : content[0]}
+ return format_out(data)
+
+@bottle.route(url_base + '/networks', method='POST')
+def http_post_networks():
+ '''insert a network into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #parse input data
+ http_content = format_in( network_new_schema )
+ r = remove_extra_items(http_content, network_new_schema)
+ if r is not None: print "http_post_networks: Warning: remove extra items ", r
+ change_keys_http2db(http_content['network'], http2db_network)
+ network=http_content['network']
+ #check valid tenant_id
+ tenant_id= network.get('tenant_id')
+ if tenant_id!=None:
+ result, _ = my.db.get_table(FROM='tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id,"enabled":True})
+ if result<=0:
+ bottle.abort(HTTP_Not_Found, 'tenant %s not found or not enabled' % tenant_id)
+ return
+ bridge_net = None
+ #check valid params
+ net_provider = network.get('provider')
+ net_type = network.get('type')
+ net_vlan = network.get("vlan")
+ net_bind_net = network.get("bind_net")
+ net_bind_type= network.get("bind_type")
+ name = network["name"]
+
+ #check if network name ends with :<vlan_tag> and network exist in order to make and automated bindning
+ vlan_index =name.rfind(":")
+ if net_bind_net==None and net_bind_type==None and vlan_index > 1:
+ try:
+ vlan_tag = int(name[vlan_index+1:])
+ if vlan_tag >0 and vlan_tag < 4096:
+ net_bind_net = name[:vlan_index]
+ net_bind_type = "vlan:" + name[vlan_index+1:]
+ except:
+ pass
+
+ if net_bind_net != None:
+ #look for a valid net
+ if check_valid_uuid(net_bind_net):
+ net_bind_key = "uuid"
+ else:
+ net_bind_key = "name"
+ result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
+ if result<0:
+ bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
+ return
+ elif result==0:
+ bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
+ return
+ elif result>1:
+ bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
+ return
+ network["bind_net"] = content[0]["uuid"]
+ if net_bind_type != None:
+ if net_bind_type[0:5] != "vlan:":
+ bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
+ return
+ if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
+ bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
+ return
+ network["bind_type"] = net_bind_type
+
+ if net_provider!=None:
+ if net_provider[:9]=="openflow:":
+ if net_type!=None:
+ if net_type!="ptp" and net_type!="data":
+ bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
+ else:
+ net_type='data'
+ else:
+ if net_type!=None:
+ if net_type!="bridge_man" and net_type!="bridge_data":
+ bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
+ else:
+ net_type='bridge_man'
+
+ if net_type==None:
+ net_type='bridge_man'
+
+ if net_provider != None:
+ if net_provider[:7]=='bridge:':
+ #check it is one of the pre-provisioned bridges
+ bridge_net_name = net_provider[7:]
+ for brnet in config_dic['bridge_nets']:
+ if brnet[0]==bridge_net_name: # free
+ if brnet[3] != None:
+ bottle.abort(HTTP_Conflict, "invalid 'provider:physical', bridge '%s' is already used" % bridge_net_name)
+ return
+ bridge_net=brnet
+ net_vlan = brnet[1]
+ break
+# if bridge_net==None:
+# bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
+# return
+ elif net_type=='bridge_data' or net_type=='bridge_man':
+ #look for a free precreated nets
+ for brnet in config_dic['bridge_nets']:
+ if brnet[3]==None: # free
+ if bridge_net != None:
+ if net_type=='bridge_man': #look for the smaller speed
+ if brnet[2] < bridge_net[2]: bridge_net = brnet
+ else: #look for the larger speed
+ if brnet[2] > bridge_net[2]: bridge_net = brnet
+ else:
+ bridge_net = brnet
+ net_vlan = brnet[1]
+ if bridge_net==None:
+ bottle.abort(HTTP_Bad_Request, "Max limits of bridge networks reached. Future versions of VIM will overcome this limit")
+ return
+ else:
+ print "using net", bridge_net
+ net_provider = "bridge:"+bridge_net[0]
+ net_vlan = bridge_net[1]
+ if net_vlan==None and (net_type=="data" or net_type=="ptp"):
+ net_vlan = my.db.get_free_net_vlan()
+ if net_vlan < 0:
+ bottle.abort(HTTP_Internal_Server_Error, "Error getting an available vlan")
+ return
+
+ network['provider'] = net_provider
+ network['type'] = net_type
+ network['vlan'] = net_vlan
+ result, content = my.db.new_row('nets', network, True, True)
+
+ if result >= 0:
+ if bridge_net!=None:
+ bridge_net[3] = content
+
+ if config_dic.get("dhcp_server"):
+ if network["name"] in config_dic["dhcp_server"].get("nets", () ):
+ config_dic["dhcp_nets"].append(content)
+ print "dhcp_server: add new net", content
+ elif bridge_net != None and bridge_net[0] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
+ config_dic["dhcp_nets"].append(content)
+ print "dhcp_server: add new net", content
+ return http_get_network_id(content)
+ else:
+ print "http_post_networks error %d %s" % (result, content)
+ bottle.abort(-result, content)
+ return
+
+
+@bottle.route(url_base + '/networks/<network_id>', method='PUT')
+def http_put_network_id(network_id):
+ '''update a network_id into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #parse input data
+ http_content = format_in( network_update_schema )
+ r = remove_extra_items(http_content, network_update_schema)
+ change_keys_http2db(http_content['network'], http2db_network)
+ network=http_content['network']
+
+ #Look for the previous data
+ where_ = {'uuid': network_id}
+ result, network_old = my.db.get_table(FROM='nets', WHERE=where_)
+ if result < 0:
+ print "http_put_network_id error %d %s" % (result, network_old)
+ bottle.abort(-result, network_old)
+ return
+ elif result==0:
+ print "http_put_network_id network '%s' not found" % network_id
+ bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
+ return
+ #get ports
+ nbports, content = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',),
+ WHERE={'net_id': network_id}, LIMIT=100)
+ if result < 0:
+ print "http_put_network_id error %d %s" % (result, network_old)
+ bottle.abort(-result, content)
+ return
+ if nbports>0:
+ if 'type' in network and network['type'] != network_old[0]['type']:
+ bottle.abort(HTTP_Method_Not_Allowed, "Can not change type of network while having ports attached")
+ if 'vlan' in network and network['vlan'] != network_old[0]['vlan']:
+ bottle.abort(HTTP_Method_Not_Allowed, "Can not change vlan of network while having ports attached")
+
+ #check valid params
+ net_provider = network.get('provider', network_old[0]['provider'])
+ net_type = network.get('type', network_old[0]['type'])
+ net_bind_net = network.get("bind_net")
+ net_bind_type= network.get("bind_type")
+ if net_bind_net != None:
+ #look for a valid net
+ if check_valid_uuid(net_bind_net):
+ net_bind_key = "uuid"
+ else:
+ net_bind_key = "name"
+ result, content = my.db.get_table(FROM='nets', WHERE={net_bind_key: net_bind_net} )
+ if result<0:
+ bottle.abort(HTTP_Internal_Server_Error, 'getting nets from db ' + content)
+ return
+ elif result==0:
+ bottle.abort(HTTP_Bad_Request, "bind_net %s '%s'not found" % (net_bind_key, net_bind_net) )
+ return
+ elif result>1:
+ bottle.abort(HTTP_Bad_Request, "more than one bind_net %s '%s' found, use uuid" % (net_bind_key, net_bind_net) )
+ return
+ network["bind_net"] = content[0]["uuid"]
+ if net_bind_type != None:
+ if net_bind_type[0:5] != "vlan:":
+ bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>'")
+ return
+ if int(net_bind_type[5:]) > 4095 or int(net_bind_type[5:])<=0 :
+ bottle.abort(HTTP_Bad_Request, "bad format for 'bind_type', must be 'vlan:<tag>' with a tag between 1 and 4095")
+ return
+ if net_provider!=None:
+ if net_provider[:9]=="openflow:":
+ if net_type!="ptp" and net_type!="data":
+ bottle.abort(HTTP_Bad_Request, "Only 'ptp' or 'data' net types can be bound to 'openflow'")
+ else:
+ if net_type!="bridge_man" and net_type!="bridge_data":
+ bottle.abort(HTTP_Bad_Request, "Only 'bridge_man' or 'bridge_data' net types can be bound to 'bridge', 'macvtap' or 'default")
+
+ #insert in data base
+ result, content = my.db.update_rows('nets', network, WHERE={'uuid': network_id}, log=True )
+ if result >= 0:
+ if result>0: # and nbports>0 and 'admin_state_up' in network and network['admin_state_up'] != network_old[0]['admin_state_up']:
+ r,c = config_dic['of_thread'].insert_task("update-net", network_id)
+ if r < 0:
+ print "http_put_network_id error while launching openflow rules"
+ bottle.abort(HTTP_Internal_Server_Error, c)
+ if config_dic.get("dhcp_server"):
+ if network_id in config_dic["dhcp_nets"]:
+ config_dic["dhcp_nets"].remove(network_id)
+ print "dhcp_server: delete net", network_id
+ if network.get("name", network_old["name"]) in config_dic["dhcp_server"].get("nets", () ):
+ config_dic["dhcp_nets"].append(network_id)
+ print "dhcp_server: add new net", network_id
+ else:
+ net_bind = network.get("bind", network_old["bind"] )
+ if net_bind and net_bind[:7]=="bridge:" and net_bind[7:] in config_dic["dhcp_server"].get("bridge_ifaces", () ):
+ config_dic["dhcp_nets"].append(network_id)
+ print "dhcp_server: add new net", network_id
+ return http_get_network_id(network_id)
+ else:
+ bottle.abort(-result, content)
+ return
+
+
+@bottle.route(url_base + '/networks/<network_id>', method='DELETE')
+def http_delete_network_id(network_id):
+ '''delete a network_id from the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+
+ #delete from the data base
+ result, content = my.db.delete_row('nets', network_id )
+
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ for brnet in config_dic['bridge_nets']:
+ if brnet[3]==network_id:
+ brnet[3]=None
+ break
+ if config_dic.get("dhcp_server") and network_id in config_dic["dhcp_nets"]:
+ config_dic["dhcp_nets"].remove(network_id)
+ print "dhcp_server: delete net", network_id
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_network_id error",result, content
+ bottle.abort(-result, content)
+ return
+#
+# OPENFLOW
+#
+@bottle.route(url_base + '/networks/<network_id>/openflow', method='GET')
+def http_get_openflow_id(network_id):
+ '''To obtain the list of openflow rules of a network
+ '''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #ignore input data
+ if network_id=='all':
+ where_={}
+ else:
+ where_={"net_id": network_id}
+ result, content = my.db.get_table(SELECT=("name","net_id","priority","vlan_id","ingress_port","src_mac","dst_mac","actions"),
+ WHERE=where_, FROM='of_flows')
+ if result < 0:
+ bottle.abort(-result, content)
+ return
+ data={'openflow-rules' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/networks/<network_id>/openflow', method='PUT')
+def http_put_openflow_id(network_id):
+ '''To make actions over the net. The action is to reinstall the openflow rules
+ network_id can be 'all'
+ '''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ return
+ #ignore input data
+ if network_id=='all':
+ where_={}
+ else:
+ where_={"uuid": network_id}
+ result, content = my.db.get_table(SELECT=("uuid","type"), WHERE=where_, FROM='nets')
+ if result < 0:
+ bottle.abort(-result, content)
+ return
+
+ for net in content:
+ if net["type"]!="ptp" and net["type"]!="data":
+ result-=1
+ continue
+ r,c = config_dic['of_thread'].insert_task("update-net", net['uuid'])
+ if r < 0:
+ print "http_put_openflow_id error while launching openflow rules"
+ bottle.abort(HTTP_Internal_Server_Error, c)
+ data={'result' : str(result)+" nets updates"}
+ return format_out(data)
+
+@bottle.route(url_base + '/networks/openflow/clear', method='DELETE')
+@bottle.route(url_base + '/networks/clear/openflow', method='DELETE')
+def http_clear_openflow_rules():
+ '''To make actions over the net. The action is to delete ALL openflow rules
+ '''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ return
+ #ignore input data
+ r,c = config_dic['of_thread'].insert_task("clear-all")
+ if r < 0:
+ print "http_delete_openflow_id error while launching openflow rules"
+ bottle.abort(HTTP_Internal_Server_Error, c)
+ return
+
+ data={'result' : " Clearing openflow rules in process"}
+ return format_out(data)
+
+@bottle.route(url_base + '/networks/openflow/ports', method='GET')
+def http_get_openflow_ports():
+ '''Obtain switch ports names of openflow controller
+ '''
+ data={'ports' : config_dic['of_thread'].OF_connector.pp2ofi}
+ return format_out(data)
+
+
+#
+# PORTS
+#
+
+@bottle.route(url_base + '/ports', method='GET')
+def http_get_ports():
+ #obtain data
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_port,
+ ('id','name','tenant_id','network_id','vpci','mac_address','device_owner','device_id',
+ 'binding:switch_port','binding:vlan','bandwidth','status','admin_state_up','ip_address') )
+ #result, content = my.db.get_ports(where_)
+ result, content = my.db.get_table(SELECT=select_, WHERE=where_, FROM='ports',LIMIT=limit_)
+ if result < 0:
+ print "http_get_ports Error", result, content
+ bottle.abort(-result, content)
+ return
+ else:
+ convert_boolean(content, ('admin_state_up',) )
+ delete_nulls(content)
+ change_keys_http2db(content, http2db_port, reverse=True)
+ data={'ports' : content}
+ return format_out(data)
+
+@bottle.route(url_base + '/ports/<port_id>', method='GET')
+def http_get_port_id(port_id):
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #obtain data
+ result, content = my.db.get_table(WHERE={'uuid': port_id}, FROM='ports')
+ if result < 0:
+ print "http_get_ports error", result, content
+ bottle.abort(-result, content)
+ elif result==0:
+ print "http_get_ports port '%s' not found" % str(port_id)
+ bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
+ else:
+ convert_boolean(content, ('admin_state_up',) )
+ delete_nulls(content)
+ change_keys_http2db(content, http2db_port, reverse=True)
+ data={'port' : content[0]}
+ return format_out(data)
+
+
+@bottle.route(url_base + '/ports', method='POST')
+def http_post_ports():
+ '''insert an external port into the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ #parse input data
+ http_content = format_in( port_new_schema )
+ r = remove_extra_items(http_content, port_new_schema)
+ if r is not None: print "http_post_ports: Warning: remove extra items ", r
+ change_keys_http2db(http_content['port'], http2db_port)
+ port=http_content['port']
+
+ port['type'] = 'external'
+ if 'net_id' in port and port['net_id'] == None:
+ del port['net_id']
+
+ if 'net_id' in port:
+ #check that new net has the correct type
+ result, new_net = my.db.check_target_net(port['net_id'], None, 'external' )
+ if result < 0:
+ bottle.abort(HTTP_Bad_Request, new_net)
+ return
+ #insert in data base
+ result, uuid = my.db.new_row('ports', port, True, True)
+ if result > 0:
+ if 'net_id' in port:
+ r,c = config_dic['of_thread'].insert_task("update-net", port['net_id'])
+ if r < 0:
+ print "http_post_ports error while launching openflow rules"
+ bottle.abort(HTTP_Internal_Server_Error, c)
+ return http_get_port_id(uuid)
+ else:
+ bottle.abort(-result, uuid)
+ return
+
+@bottle.route(url_base + '/ports/<port_id>', method='PUT')
+def http_put_port_id(port_id):
+ '''update a port_id into the database.'''
+
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ #parse input data
+ http_content = format_in( port_update_schema )
+ change_keys_http2db(http_content['port'], http2db_port)
+ port_dict=http_content['port']
+
+ #Look for the previous port data
+ where_ = {'uuid': port_id}
+ result, content = my.db.get_table(FROM="ports",WHERE=where_)
+ if result < 0:
+ print "http_put_port_id error", result, content
+ bottle.abort(-result, content)
+ return
+ elif result==0:
+ print "http_put_port_id port '%s' not found" % port_id
+ bottle.abort(HTTP_Not_Found, 'port %s not found' % port_id)
+ return
+ print port_dict
+ for k in ('vlan','switch_port','mac_address', 'tenant_id'):
+ if k in port_dict and not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges for changing " + k)
+ return
+
+ port=content[0]
+ #change_keys_http2db(port, http2db_port, reverse=True)
+ nets = []
+ host_id = None
+ result=1
+ if 'net_id' in port_dict:
+ #change of net.
+ old_net = port.get('net_id', None)
+ new_net = port_dict['net_id']
+ if old_net != new_net:
+
+ if new_net is not None: nets.append(new_net) #put first the new net, so that new openflow rules are created before removing the old ones
+ if old_net is not None: nets.append(old_net)
+ if port['type'] == 'instance:bridge':
+ bottle.abort(HTTP_Forbidden, "bridge interfaces cannot be attached to a different net")
+ return
+ elif port['type'] == 'external':
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ return
+ else:
+ if new_net != None:
+ #check that new net has the correct type
+ result, new_net_dict = my.db.check_target_net(new_net, None, port['type'] )
+
+ #change VLAN for SR-IOV ports
+ if result>=0 and port["type"]=="instance:data" and port["model"]=="VF": #TODO consider also VFnotShared
+ if new_net == None:
+ port_dict["vlan"] = None
+ else:
+ port_dict["vlan"] = new_net_dict["vlan"]
+ #get host where this VM is allocated
+ result, content = my.db.get_table(FROM="instances",WHERE={"uuid":port["instance_id"]})
+ if result<0:
+ print "http_put_port_id database error", content
+ elif result>0:
+ host_id = content[0]["host_id"]
+
+ #insert in data base
+ if result >= 0:
+ result, content = my.db.update_rows('ports', port_dict, WHERE={'uuid': port_id}, log=False )
+
+ #Insert task to complete actions
+ if result > 0:
+ for net_id in nets:
+ r,v = config_dic['of_thread'].insert_task("update-net", net_id)
+ if r<0: print "Error ********* http_put_port_id update_of_flows: ", v
+ #TODO Do something if fails
+ if host_id != None:
+ config_dic['host_threads'][host_id].insert_task("edit-iface", port_id, old_net, new_net)
+
+ if result >= 0:
+ return http_get_port_id(port_id)
+ else:
+ bottle.abort(HTTP_Bad_Request, content)
+ return
+
+
+@bottle.route(url_base + '/ports/<port_id>', method='DELETE')
+def http_delete_port_id(port_id):
+ '''delete a port_id from the database.'''
+ my = config_dic['http_threads'][ threading.current_thread().name ]
+ if not my.admin:
+ bottle.abort(HTTP_Unauthorized, "Needed admin privileges")
+ return
+
+ #Look for the previous port data
+ where_ = {'uuid': port_id, "type": "external"}
+ result, ports = my.db.get_table(WHERE=where_, FROM='ports',LIMIT=100)
+
+ if result<=0:
+ print "http_delete_port_id port '%s' not found" % port_id
+ bottle.abort(HTTP_Not_Found, 'port %s not found or device_owner is not external' % port_id)
+ return
+ #delete from the data base
+ result, content = my.db.delete_row('ports', port_id )
+
+ if result == 0:
+ bottle.abort(HTTP_Not_Found, content)
+ elif result >0:
+ network = ports[0].get('net_id', None)
+ if network is not None:
+ #change of net.
+ r,c = config_dic['of_thread'].insert_task("update-net", network)
+ if r<0: print "!!!!!! http_delete_port_id update_of_flows error", r, c
+ data={'result' : content}
+ return format_out(data)
+ else:
+ print "http_delete_port_id error",result, content
+ bottle.abort(-result, content)
+ return
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# PYTHON_ARGCOMPLETE_OK
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This program is useful to interact directly with Openflow Controllers
+to clear rules, add and delete rules, list rules, etc.
+'''
+
+__author__="Gerardo Garcia, Alfonso Tierno, Pablo Montes"
+__date__ ="$09-oct-2014 09:09:48$"
+
+#import time
+import os
+import sys
+import argparse
+import argcomplete
+import imp
+import yaml
+import requests
+import logging
+from openflow_thread import change_db2of, FlowBadFormat
+
+def of_switches(args):
+ r,c = ofconnector.get_of_switches()
+ if r<0:
+ print c
+ return r
+ else:
+ for s in c:
+ print " %s %s" % (s[0], s[1])
+ return 0
+
+def of_list(args):
+ r,c = ofconnector.get_of_rules(not args.no_translate)
+ if r<0:
+ print c
+ return r
+ if args.verbose > 0:
+ print yaml.safe_dump(c, indent=4, default_flow_style=False)
+ return 0
+
+ print " switch priority name ingress_port dst_mac vlan_id actions"
+ for name,rule in c.iteritems():
+ action_list=[]
+ for action in rule["actions"]:
+ action_list.append(action[0]+"="+str(action[1]))
+ if "vlan_id" in rule:
+ vlan=str(rule["vlan_id"])
+ else:
+ vlan="any"
+ print "%s %s %s %s %s %s %s" % \
+ (rule["switch"], str(rule["priority"]).ljust(6), name.ljust(40), rule["ingress_port"].ljust(8), \
+ rule.get("dst_mac","any").ljust(18), vlan.ljust(4), ",".join(action_list) )
+ return 0
+
+def of_clear(args):
+ if not args.force:
+ r = raw_input("Clear all Openflow rules (y/N)? ")
+ if not (len(r)>0 and r[0].lower()=="y"):
+ return 0
+ r,c = ofconnector.clear_all_flows()
+ if r<0:
+ print c
+ return r
+ return 0
+
+def of_port_list(args):
+ r,c = ofconnector.obtain_port_correspondence()
+ if r<0:
+ print c
+ return r
+ yaml.safe_dump({"ports": c}, sys.stdout, indent=2, default_flow_style=False)
+
+#def of_dump(args):
+# args.verbose = 3
+# args.no_translate=False
+# of_list(args)
+ return 0
+
+def of_reinstall(args):
+ try:
+ URLrequest = "http://%s:%s/openvim/networks/all/openflow" %(vim_host, vim_admin_port)
+ print URLrequest
+ openvim_response = requests.put(URLrequest)
+ print openvim_response.text
+ return 0
+ except requests.exceptions.RequestException as e:
+ print " Exception GET at '"+URLrequest+"' " + str(e)
+ return -1
+
+def of_install(args):
+ line_number=1
+ try:
+ f = open(args.file, "r")
+ text = f.read()
+ f.close()
+ lines=text.split("\n")
+ heads=lines[0].split()
+
+ for line in lines[1:]:
+ line_number += 1
+ rule={}
+ items= line.split()
+ if len(items)==0 or items[0][0]=="#": #empty line or commented
+ continue
+ for i in range(0,len(items)):
+ rule[ heads[i] ] = items[i]
+ if rule["vlan_id"] == "any":
+ del rule["vlan_id"]
+ if rule["dst_mac"] == "any":
+ del rule["dst_mac"]
+ if 'priority' in rule and (rule['priority']==None or rule['priority']=="None" ):
+ del rule['priority']
+ try:
+ change_db2of(rule)
+ except FlowBadFormat as e:
+ print "Format error at line %d: %s" % (line_number, str(e))
+ continue
+ r,c = ofconnector.new_flow(rule)
+ if r<0:
+ error="ERROR: "+c
+ else:
+ error="OK"
+ print "%s %s %s input=%s dst_mac=%s vlan_id=%s %s" % \
+ (rule["switch"], str(rule.get("priority")).ljust(6), rule["name"].ljust(20), rule["ingress_port"].ljust(3), \
+ rule.get("dst_mac","any").ljust(18), rule.get("vlan_id","any").ljust(4), error )
+ return 0
+ except IOError as e:
+ print " Error opening file '" + args.file + "': " + e.args[1]
+ return -1
+ except yaml.YAMLError as exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print " Error yaml/json format error at " + error_pos
+ return -1
+
+def of_add(args):
+ if args.act==None and args.actions==None:
+ print "openflow add: error: one of the arguments --actions or [--setvlan,--stripvlan],--out is required"
+ return -1
+ elif args.act!=None and args.actions!=None:
+ print "openflow add: error: Use either --actions option or [--setvlan,--stripvlan],--out options; but not both"
+ return -1
+
+ rule={"name":args.name, "priority":args.priority,
+ "ingress_port": args.inport
+ }
+ if args.matchvlan:
+ rule["vlan_id"] = args.matchvlan
+ if args.matchmac:
+ rule["dst_mac"] = args.matchmac
+
+ if args.actions:
+ rule["actions"] = args.actions
+ try:
+ change_db2of(rule)
+ except FlowBadFormat as e:
+ print "Format error at --actions: '%s' Expected 'vlan=<None/vlan_id>,out=<egress_port>,...'" % str(e)
+ return -1
+ elif args.act:
+ rule["actions"]=[]
+ error_msj = "openflow add: error: --setvlan,--stripvlan options must be followed by an --out option"
+ previous_option_vlan=False # indicates if the previous option was a set or strip vlan to avoid consecutive ones and to force an out options afterwards
+ for action in args.act:
+ if action==None or type(action)==int:
+ if previous_option_vlan: #consecutive vlan options
+ print error_msj
+ return -1
+ previous_option_vlan=True
+ rule["actions"].append( ("vlan", action) )
+ else:
+ previous_option_vlan=False
+ rule["actions"].append( ("out", action) )
+ if previous_option_vlan:
+ print error_msj
+ return -1
+ #print rule
+ #return
+
+ r,c = ofconnector.new_flow(rule)
+ if r<0:
+ print c
+ return -1
+ return 0
+
+def of_delete(args):
+ if not args.force:
+ r = raw_input("Clear rule %s (y/N)? " %(args.name))
+ if not (len(r)>0 and r[0].lower()=="y"):
+ return 0
+ r,c = ofconnector.del_flow(args.name)
+ if r<0:
+ print c
+ return -1
+ return 0
+
+def config(args):
+ print "OPENVIM_HOST: %s" %(vim_host)
+ print "OPENVIM_ADMIN_PORT: %s" %(vim_admin_port)
+ print "OF_CONTROLLER_TYPE: %s" %(of_controller_type)
+ if of_controller_module or (of_controller_type!="floodlight" and of_controller_type!="opendaylight"):
+ print "OF_CONTROLLER_MODULE: %s" %(of_controller_module)
+ print "OF_CONTROLLER_USER: %s" %(of_controller_user)
+ print "OF_CONTROLLER_PASSWORD: %s" %(of_controller_password)
+ #print "OF_CONTROLLER_VERSION: %s" %(of_controller_version)
+ print "OF_CONTROLLER_IP: %s" %(of_controller_ip)
+ print "OF_CONTROLLER_PORT: %s" %(of_controller_port)
+ print "OF_CONTROLLER_DPID: %s" %(of_controller_dpid)
+ return
+
+version="0.8"
+global vim_host
+global vim_admin_port
+global of_controller_type
+global of_controller_user
+global of_controller_password
+global of_controller_ip
+global of_controller_port
+global of_controller_dpid
+global of_controller_module
+global ofconnector
+
+if __name__=="__main__":
+ #print "test_ofconnector version", version, "Jul 2015"
+ #print "(c) Copyright Telefonica"
+
+ vim_host = os.getenv('OPENVIM_HOST',"localhost")
+ vim_admin_port = os.getenv('OPENVIM_ADMIN_PORT',"8085")
+ of_controller_type = os.getenv('OF_CONTROLLER_TYPE',"floodlight")
+ of_controller_user = os.getenv('OF_CONTROLLER_USER',None)
+ of_controller_password = os.getenv('OF_CONTROLLER_PASSWORD',None)
+ #of_controller_version = os.getenv('OF_CONTROLLER_VERSION',"0.90")
+ of_controller_ip = os.getenv('OF_CONTROLLER_IP',"localhost")
+ of_controller_port = os.getenv('OF_CONTROLLER_PORT',"7070")
+ of_controller_dpid = os.getenv('OF_CONTROLLER_DPID','00:01:02:03:e4:05:e6:07')
+ of_controller_module = os.getenv('OF_CONTROLLER_MODULE',None)
+
+ main_parser = argparse.ArgumentParser(description='User program to interact with Openflow controller')
+ main_parser.add_argument('--version', action='version', version='%(prog)s ' + version )
+
+ #main_parser = argparse.ArgumentParser()
+ subparsers = main_parser.add_subparsers(help='commands')
+
+ config_parser = subparsers.add_parser('config', help="prints configuration values")
+ config_parser.set_defaults(func=config)
+
+ add_parser = subparsers.add_parser('add', help="adds an openflow rule")
+ add_parser.add_argument('--verbose', '-v', action='count')
+ add_parser.add_argument("name", action="store", help="name of the rule")
+ add_parser.add_argument("--inport", required=True, action="store", type=str, help="match rule: ingress-port")
+ add_parser.add_argument("--actions", action="store", type=str, help="action with the format: vlan=<None/vlan-id>,out=<egress-port>,...")
+ add_parser.add_argument("--priority", action="store", type=int, help="rule priority")
+ add_parser.add_argument("--matchmac", action="store", help="match rule: mac address")
+ add_parser.add_argument("--matchvlan", action="store", type=int, help="match rule: vlan id")
+ add_parser.add_argument("--stripvlan", action="append_const", dest="act", const=None, help="alternative to --actions. Use before --out to strip vlan")
+ add_parser.add_argument("--setvlan", action="append", dest="act", type=int, help="alternative to --actions. Use before --out to set vlan")
+ add_parser.add_argument("--out", action="append", dest="act", type=str, help="alternative to --actions. out=<egress-port> can be used several times")
+ add_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ add_parser.set_defaults(func=of_add)
+
+ delete_parser = subparsers.add_parser('delete', help="delete an openflow rule")
+ delete_parser.add_argument('--verbose', '-v', action='count')
+ delete_parser.add_argument("-f", "--force", action="store_true", help="force deletion without asking")
+ delete_parser.add_argument("name", action="store", help="name of the rule to be deleted")
+ delete_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ delete_parser.set_defaults(func=of_delete)
+
+ switches_parser = subparsers.add_parser('switches', help="list all switches controlled by the OFC")
+ switches_parser.add_argument('--verbose', '-v', action='count')
+ switches_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ switches_parser.set_defaults(func=of_switches)
+
+ list_parser = subparsers.add_parser('list', help="list openflow rules")
+ list_parser.add_argument('--verbose', '-v', action='count')
+ list_parser.add_argument("--no-translate", "-n", action="store_true", help="Skip translation from openflow index to switch port name")
+ list_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ list_parser.set_defaults(func=of_list)
+
+ #dump_parser = subparsers.add_parser('dump', help="dump openflow rules")
+ #dump_parser.set_defaults(func=of_dump)
+
+ clear_parser = subparsers.add_parser('clear', help="clear all openflow rules")
+ clear_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking")
+ clear_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ clear_parser.set_defaults(func=of_clear)
+
+ install_parser = subparsers.add_parser('install', help="install openflow rules from file")
+ install_parser.add_argument("file", action="store", help="file with rules generated using 'openflow list > rules.txt'")
+ install_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+ install_parser.set_defaults(func=of_install)
+
+ reinstall_parser = subparsers.add_parser('reinstall', help="reinstall openflow rules from VIM rules")
+ reinstall_parser.set_defaults(func=of_reinstall)
+ reinstall_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+
+ portlist_parser = subparsers.add_parser('port-list', help="list the physical to openflow port correspondence")
+ portlist_parser.set_defaults(func=of_port_list)
+ portlist_parser.add_argument('--debug', '-d', action='store_true', help="show debug information")
+
+ argcomplete.autocomplete(main_parser)
+
+ args = main_parser.parse_args()
+ module_info=None
+ try:
+ if args.func is not config:
+ params={ "of_ip": of_controller_ip,
+ "of_port": of_controller_port,
+ "of_dpid": of_controller_dpid,
+ "of_user": of_controller_user,
+ "of_password": of_controller_password,
+ }
+ if "debug" in args and args.debug:
+ streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
+ logging.basicConfig(format=streamformat, level= logging.DEBUG)
+ logger = logging.getLogger('vim')
+ logger.setLevel(logging.DEBUG)
+ params["of_debug"]="DEBUG"
+ else:
+ #logger = logging.getLogger('vim').addHandler(logging.NullHandler())
+ #logger.setLevel(logging.CRITICAL)
+ params["of_debug"]="CRITICAL"
+
+ if of_controller_type=='opendaylight':
+ module = "ODL"
+ elif of_controller_module != None:
+ module = of_controller_module
+ else:
+ module = of_controller_type
+ module_info = imp.find_module(module)
+
+ of_conn = imp.load_module("of_conn", *module_info)
+ try:
+ ofconnector = of_conn.OF_conn(params)
+ except Exception as e:
+ print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
+ result = -1
+ exit()
+ result = args.func(args)
+ if result == None:
+ result = 0
+
+ #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
+ except (IOError, ImportError) as e:
+ print "Cannot open openflow controller module '%s'; %s: %s" % (module, type(e).__name__, str(e))
+ result = -1
+ #except Exception as e:
+ # print "Cannot open the Openflow controller '%s': %s" % (type(e).__name__, str(e))
+ # result = -1
+ except requests.exceptions.ConnectionError as e:
+ print "Cannot connect to server; %s: %s" % (type(e).__name__, str(e))
+ result = -2
+ except (KeyboardInterrupt):
+ print 'Exiting openVIM'
+ result = -3
+ except (SystemExit):
+ result = -4
+
+ #close open file
+ if module_info and module_info[0]:
+ file.close(module_info[0])
+ exit(result)
+
+
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This thread interacts with a openflow floodligth controller to create dataplane connections
+'''
+
+__author__="Pablo Montes, Alfonso Tierno"
+__date__ ="17-jul-2015"
+
+
+#import json
+import threading
+import time
+import Queue
+import requests
+import logging
+
+class FlowBadFormat(Exception):
+ '''raise when a bad format of flow is found'''
+
+def change_of2db(flow):
+ '''Change 'flow' dictionary from openflow format to database format
+ Basically the change consist of changing 'flow[actions] from a list of
+ double tuple to a string
+ from [(A,B),(C,D),..] to "A=B,C=D" '''
+ action_str_list=[]
+ if type(flow)!=dict or "actions" not in flow:
+ raise FlowBadFormat("Bad input parameters, expect dictionary with 'actions' as key")
+ try:
+ for action in flow['actions']:
+ action_str_list.append( action[0] + "=" + str(action[1]) )
+ flow['actions'] = ",".join(action_str_list)
+ except:
+ raise FlowBadFormat("Unexpected format at 'actions'")
+
+def change_db2of(flow):
+ '''Change 'flow' dictionary from database format to openflow format
+ Basically the change consist of changing 'flow[actions]' from a string to
+ a double tuple list
+ from "A=B,C=D,..." to [(A,B),(C,D),..]
+ raise FlowBadFormat '''
+ actions=[]
+ if type(flow)!=dict or "actions" not in flow or type(flow["actions"])!=str:
+ raise FlowBadFormat("Bad input parameters, expect dictionary with 'actions' as key")
+ action_list = flow['actions'].split(",")
+ for action_item in action_list:
+ action_tuple = action_item.split("=")
+ if len(action_tuple) != 2:
+ raise FlowBadFormat("Expected key=value format at 'actions'")
+ if action_tuple[0].strip().lower()=="vlan":
+ if action_tuple[1].strip().lower() in ("none", "strip"):
+ actions.append( ("vlan",None) )
+ else:
+ try:
+ actions.append( ("vlan", int(action_tuple[1])) )
+ except:
+ raise FlowBadFormat("Expected integer after vlan= at 'actions'")
+ elif action_tuple[0].strip().lower()=="out":
+ actions.append( ("out", str(action_tuple[1])) )
+ else:
+ raise FlowBadFormat("Unexpected '%s' at 'actions'"%action_tuple[0])
+ flow['actions'] = actions
+
+
+
+class of_test_connector():
+ '''This is a fake openflow connector for testing.
+ It does nothing and it is used for running openvim without an openflow controller
+ '''
+ def __init__(self, params):
+ self.name = "ofc_test"
+ self.rules={}
+ self.logger = logging.getLogger('vim.OF.TEST')
+ self.logger.setLevel( getattr(logging, params.get("of_debug", "ERROR") ) )
+ def get_of_switches(self):
+ return 0, ()
+ def obtain_port_correspondence(self):
+ return 0, ()
+ def del_flow(self, flow_name):
+ if flow_name in self.rules:
+ self.logger.debug("del_flow OK")
+ del self.rules[flow_name]
+ return 0, None
+ else:
+ self.logger.warning("del_flow not found")
+ return -1, "flow %s not found"
+ def new_flow(self, data):
+ self.rules[ data["name"] ] = data
+ self.logger.debug("new_flow OK")
+ return 0, None
+ def get_of_rules(self, translate_of_ports=True):
+ return 0, self.rules
+
+ def clear_all_flows(self):
+ self.logger.debug("clear_all_flows OK")
+ self.rules={}
+ return 0, None
+
+
+
+class openflow_thread(threading.Thread):
+ def __init__(self, OF_connector, db, db_lock, of_test, pmp_with_same_vlan, debug='ERROR'):
+ threading.Thread.__init__(self)
+
+ self.db = db
+ self.pmp_with_same_vlan = pmp_with_same_vlan
+ self.name = "openflow"
+ self.test = of_test
+ self.db_lock = db_lock
+ self.OF_connector = OF_connector
+ self.logger = logging.getLogger('vim.OF')
+ self.logger.setLevel( getattr(logging, debug) )
+
+ self.queueLock = threading.Lock()
+ self.taskQueue = Queue.Queue(2000)
+
+ def insert_task(self, task, *aditional):
+ try:
+ self.queueLock.acquire()
+ task = self.taskQueue.put( (task,) + aditional, timeout=5)
+ self.queueLock.release()
+ return 1, None
+ except Queue.Full:
+ return -1, "timeout inserting a task over openflow thread " + self.name
+
+ def run(self):
+ while True:
+ self.queueLock.acquire()
+ if not self.taskQueue.empty():
+ task = self.taskQueue.get()
+ else:
+ task = None
+ self.queueLock.release()
+
+ if task is None:
+ time.sleep(1)
+ continue
+
+ if task[0] == 'update-net':
+ r,c = self.update_of_flows(task[1])
+ #update database status
+ self.db_lock.acquire()
+ if r<0:
+ UPDATE={'status':'ERROR', 'last_error': str(c)}
+ self.logger.error("processing task 'update-net' %s: %s", str(task[1]), c)
+ else:
+ UPDATE={'status':'ACTIVE', 'last_error': None}
+ self.logger.debug("processing task 'update-net' %s: OK", str(task[1]))
+ self.db.update_rows('nets', UPDATE, WHERE={'uuid':task[1]})
+ self.db_lock.release()
+
+ elif task[0] == 'clear-all':
+ r,c = self.clear_all_flows()
+ if r<0:
+ self.logger.error("processing task 'clear-all': %s", c)
+ else:
+ self.logger.debug("processing task 'clear-all': OK")
+ elif task[0] == 'exit':
+ self.logger.debug("exit from openflow_thread")
+ self.terminate()
+ return 0
+ else:
+ self.logger.error("unknown task %s", str(task))
+
+ def terminate(self):
+ pass
+ #print self.name, ": exit from openflow_thread"
+
+ def update_of_flows(self, net_id):
+ ports=()
+ self.db_lock.acquire()
+ select_= ('type','admin_state_up', 'vlan', 'provider', 'bind_net','bind_type','uuid')
+ result, nets = self.db.get_table(FROM='nets', SELECT=select_, WHERE={'uuid':net_id} )
+ #get all the networks binding to this
+ if result > 0:
+ if nets[0]['bind_net']:
+ bind_id = nets[0]['bind_net']
+ else:
+ bind_id = net_id
+ #get our net and all bind_nets
+ result, nets = self.db.get_table(FROM='nets', SELECT=select_,
+ WHERE_OR={'bind_net':bind_id, 'uuid':bind_id} )
+
+ self.db_lock.release()
+ if result < 0:
+ return -1, "DB error getting net: " + nets
+ #elif result==0:
+ #net has been deleted
+ ifaces_nb = 0
+ database_flows = []
+ for net in nets:
+ net_id = net["uuid"]
+ if net['admin_state_up'] == 'false':
+ net['ports'] = ()
+ else:
+ self.db_lock.acquire()
+ nb_ports, net_ports = self.db.get_table(
+ FROM='ports',
+ SELECT=('switch_port','vlan','uuid','mac','type','model'),
+ WHERE={'net_id':net_id, 'admin_state_up':'true', 'status':'ACTIVE'} )
+ self.db_lock.release()
+ if nb_ports < 0:
+ #print self.name, ": update_of_flows() ERROR getting ports", ports
+ return -1, "DB error getting ports from net '%s': %s" % (net_id, net_ports)
+
+ #add the binding as an external port
+ if net['provider'] and net['provider'][:9]=="openflow:":
+ external_port={"type":"external","mac":None}
+ external_port['uuid'] = net_id + ".1" #fake uuid
+ if net['provider'][-5:]==":vlan":
+ external_port["vlan"] = net["vlan"]
+ external_port["switch_port"] = net['provider'][9:-5]
+ else:
+ external_port["vlan"] = None
+ external_port["switch_port"] = net['provider'][9:]
+ net_ports = net_ports + (external_port,)
+ nb_ports += 1
+ net['ports'] = net_ports
+ ifaces_nb += nb_ports
+
+ # Get the name of flows that will be affected by this NET
+ self.db_lock.acquire()
+ result, database_net_flows = self.db.get_table(FROM='of_flows', WHERE={'net_id':net_id})
+ self.db_lock.release()
+ if result < 0:
+ #print self.name, ": update_of_flows() ERROR getting flows from database", database_flows
+ return -1, "DB error getting flows from net '%s': %s" %(net_id, database_net_flows)
+ database_flows += database_net_flows
+ # Get the name of flows where net_id==NULL that means net deleted (At DB foreign key: On delete set null)
+ self.db_lock.acquire()
+ result, database_net_flows = self.db.get_table(FROM='of_flows', WHERE={'net_id':None})
+ self.db_lock.release()
+ if result < 0:
+ #print self.name, ": update_of_flows() ERROR getting flows from database", database_flows
+ return -1, "DB error getting flows from net 'null': %s" %(database_net_flows)
+ database_flows += database_net_flows
+
+ #Get the existing flows at openflow controller
+ result, of_flows = self.OF_connector.get_of_rules()
+ if result < 0:
+ #print self.name, ": update_of_flows() ERROR getting flows from controller", of_flows
+ return -1, "OF error getting flows: " + of_flows
+
+ if ifaces_nb < 2:
+ pass
+ elif net['type'] == 'ptp':
+ if ifaces_nb > 2:
+ #print self.name, 'Error, network '+str(net_id)+' has been defined as ptp but it has '+\
+ # str(ifaces_nb)+' interfaces.'
+ return -1, "'ptp' type network cannot connect %d interfaces, only 2" % ifaces_nb
+ elif net['type'] == 'data':
+ if ifaces_nb > 2 and self.pmp_with_same_vlan:
+ # check all ports are VLAN (tagged) or none
+ vlan_tag = None
+ for port in ports:
+ if port["type"]=="external":
+ if port["vlan"] != None:
+ if port["vlan"]!=net["vlan"]:
+ text="External port vlan-tag and net vlan-tag must be the same when flag 'of_controller_nets_with_same_vlan' is True"
+ #print self.name, "Error", text
+ return -1, text
+ if vlan_tag == None:
+ vlan_tag=True
+ elif vlan_tag==False:
+ text="Passthrough and external port vlan-tagged can not be connected when flag 'of_controller_nets_with_same_vlan' is True"
+ #print self.name, "Error", text
+ return -1, text
+ else:
+ if vlan_tag == None:
+ vlan_tag=False
+ elif vlan_tag == True:
+ text="SR-IOV and external port not vlan-tagged can not be connected when flag 'of_controller_nets_with_same_vlan' is True"
+ #print self.name, "Error", text
+ return -1, text
+ elif port["model"]=="PF" or port["model"]=="VFnotShared":
+ if vlan_tag == None:
+ vlan_tag=False
+ elif vlan_tag==True:
+ text="Passthrough and SR-IOV ports cannot be connected when flag 'of_controller_nets_with_same_vlan' is True"
+ #print self.name, "Error", text
+ return -1, text
+ elif port["model"] == "VF":
+ if vlan_tag == None:
+ vlan_tag=True
+ elif vlan_tag==False:
+ text="Passthrough and SR-IOV ports cannot be connected when flag 'of_controller_nets_with_same_vlan' is True"
+ #print self.name, "Error", text
+ return -1, text
+ else:
+ return -1, 'Only ptp and data networks are supported for openflow'
+
+ # calculate new flows to be inserted
+ result, new_flows = self._compute_net_flows(nets)
+ if result < 0:
+ return result, new_flows
+
+ #modify database flows format and get the used names
+ used_names=[]
+ for flow in database_flows:
+ try:
+ change_db2of(flow)
+ except FlowBadFormat as e:
+ self.logger.error("Exception FlowBadFormat: '%s', flow: '%s'",str(e), str(flow))
+ continue
+ used_names.append(flow['name'])
+ name_index=0
+ #insert at database the new flows, change actions to human text
+ for flow in new_flows:
+ #1 check if an equal flow is already present
+ index = self._check_flow_already_present(flow, database_flows)
+ if index>=0:
+ database_flows[index]["not delete"]=True
+ self.logger.debug("Skipping already present flow %s", str(flow))
+ continue
+ #2 look for a non used name
+ flow_name=flow["net_id"]+"."+str(name_index)
+ while flow_name in used_names or flow_name in of_flows:
+ name_index += 1
+ flow_name=flow["net_id"]+"."+str(name_index)
+ used_names.append(flow_name)
+ flow['name'] = flow_name
+ #3 insert at openflow
+ result, content = self.OF_connector.new_flow(flow)
+ if result < 0:
+ #print self.name, ": Error '%s' at flow insertion" % c, flow
+ return -1, content
+ #4 insert at database
+ try:
+ change_of2db(flow)
+ except FlowBadFormat as e:
+ #print self.name, ": Error Exception FlowBadFormat '%s'" % str(e), flow
+ return -1, str(e)
+ self.db_lock.acquire()
+ result, content = self.db.new_row('of_flows', flow)
+ self.db_lock.release()
+ if result < 0:
+ #print self.name, ": Error '%s' at database insertion" % content, flow
+ return -1, content
+
+ #delete not needed old flows from openflow and from DDBB,
+ #check that the needed flows at DDBB are present in controller or insert them otherwise
+ for flow in database_flows:
+ if "not delete" in flow:
+ if flow["name"] not in of_flows:
+ #not in controller, insert it
+ result, content = self.OF_connector.new_flow(flow)
+ if result < 0:
+ #print self.name, ": Error '%s' at flow insertion" % c, flow
+ return -1, content
+ continue
+ #Delete flow
+ if flow["name"] in of_flows:
+ result, content = self.OF_connector.del_flow(flow['name'])
+ if result<0:
+ self.logger.error("cannot delete flow '%s' from OF: %s", flow['name'], content )
+ continue #skip deletion from database
+ #delete from database
+ self.db_lock.acquire()
+ result, content = self.db.delete_row_by_key('of_flows', 'id', flow['id'])
+ self.db_lock.release()
+ if result<0:
+ self.logger.error("cannot delete flow '%s' from DB: %s", flow['name'], content )
+
+ return 0, 'Success'
+
+ def clear_all_flows(self):
+ try:
+ if not self.test:
+ self.OF_connector.clear_all_flows()
+ #remove from database
+ self.db_lock.acquire()
+ self.db.delete_row_by_key('of_flows', None, None) #this will delete all lines
+ self.db_lock.release()
+ return 0, None
+ except requests.exceptions.RequestException as e:
+ #print self.name, ": clear_all_flows Exception:", str(e)
+ return -1, str(e)
+
+ flow_fields=('priority', 'vlan', 'ingress_port', 'actions', 'dst_mac', 'src_mac', 'net_id')
+ def _check_flow_already_present(self, new_flow, flow_list):
+ '''check if the same flow is already present in the flow list
+ The flow is repeated if all the fields, apart from name, are equal
+ Return the index of matching flow, -1 if not match'''
+ index=0
+ for flow in flow_list:
+ equal=True
+ for f in self.flow_fields:
+ if flow.get(f) != new_flow.get(f):
+ equal=False
+ break
+ if equal:
+ return index
+ index += 1
+ return -1
+
+ def _compute_net_flows(self, nets):
+ new_flows=[]
+ new_broadcast_flows={}
+ nb_ports = 0
+
+ # Check switch_port information is right
+ self.logger.debug("_compute_net_flows nets: %s", str(nets))
+ for net in nets:
+ for port in net['ports']:
+ nb_ports += 1
+ if not self.test and str(port['switch_port']) not in self.OF_connector.pp2ofi:
+ error_text= "switch port name '%s' is not valid for the openflow controller" % str(port['switch_port'])
+ #print self.name, ": ERROR " + error_text
+ return -1, error_text
+
+ for net_src in nets:
+ net_id = net_src["uuid"]
+ for net_dst in nets:
+ vlan_net_in = None
+ vlan_net_out = None
+ if net_src == net_dst:
+ #intra net rules
+ priority = 1000
+ elif net_src['bind_net'] == net_dst['uuid']:
+ if net_src.get('bind_type') and net_src['bind_type'][0:5] == "vlan:":
+ vlan_net_out = int(net_src['bind_type'][5:])
+ priority = 1100
+ elif net_dst['bind_net'] == net_src['uuid']:
+ if net_dst.get('bind_type') and net_dst['bind_type'][0:5] == "vlan:":
+ vlan_net_in = int(net_dst['bind_type'][5:])
+ priority = 1100
+ else:
+ #nets not binding
+ continue
+ for src_port in net_src['ports']:
+ vlan_in = vlan_net_in
+ if vlan_in == None and src_port['vlan'] != None:
+ vlan_in = src_port['vlan']
+ elif vlan_in != None and src_port['vlan'] != None:
+ #TODO this is something that we can not do. It requires a double VLAN check
+ #outer VLAN should be src_port['vlan'] and inner VLAN should be vlan_in
+ continue
+
+ # BROADCAST:
+ broadcast_key = src_port['uuid'] + "." + str(vlan_in)
+ if broadcast_key in new_broadcast_flows:
+ flow_broadcast = new_broadcast_flows[broadcast_key]
+ else:
+ flow_broadcast = {'priority': priority,
+ 'net_id': net_id,
+ 'dst_mac': 'ff:ff:ff:ff:ff:ff',
+ "ingress_port": str(src_port['switch_port']),
+ 'actions': []
+ }
+ new_broadcast_flows[broadcast_key] = flow_broadcast
+ if vlan_in is not None:
+ flow_broadcast['vlan_id'] = str(vlan_in)
+
+ for dst_port in net_dst['ports']:
+ vlan_out = vlan_net_out
+ if vlan_out == None and dst_port['vlan'] != None:
+ vlan_out = dst_port['vlan']
+ elif vlan_out != None and dst_port['vlan'] != None:
+ #TODO this is something that we can not do. It requires a double VLAN set
+ #outer VLAN should be dst_port['vlan'] and inner VLAN should be vlan_out
+ continue
+ #if src_port == dst_port:
+ # continue
+ if src_port['switch_port'] == dst_port['switch_port'] and vlan_in == vlan_out:
+ continue
+ flow = {
+ "priority": priority,
+ 'net_id': net_id,
+ "ingress_port": str(src_port['switch_port']),
+ 'actions': []
+ }
+ if vlan_in is not None:
+ flow['vlan_id'] = str(vlan_in)
+ # allow that one port have no mac
+ if dst_port['mac'] is None or nb_ports==2: # point to point or nets with 2 elements
+ flow['priority'] = priority-5 # less priority
+ else:
+ flow['dst_mac'] = str(dst_port['mac'])
+
+ if vlan_out == None:
+ if vlan_in != None:
+ flow['actions'].append( ('vlan',None) )
+ else:
+ flow['actions'].append( ('vlan', vlan_out ) )
+ flow['actions'].append( ('out', str(dst_port['switch_port'])) )
+
+ if self._check_flow_already_present(flow, new_flows) >= 0:
+ self.logger.debug("Skipping repeated flow '%s'", str(flow))
+ continue
+
+ new_flows.append(flow)
+
+ # BROADCAST:
+ if nb_ports <= 2: # point to multipoint or nets with more than 2 elements
+ continue
+ out = (vlan_out, str(dst_port['switch_port']))
+ if out not in flow_broadcast['actions']:
+ flow_broadcast['actions'].append( out )
+
+ #BROADCAST
+ for flow_broadcast in new_broadcast_flows.values():
+ if len(flow_broadcast['actions'])==0:
+ continue #nothing to do, skip
+ flow_broadcast['actions'].sort()
+ if 'vlan_id' in flow_broadcast:
+ previous_vlan = 0 # indicates that a packet contains a vlan, and the vlan
+ else:
+ previous_vlan = None
+ final_actions=[]
+ action_number = 0
+ for action in flow_broadcast['actions']:
+ if action[0] != previous_vlan:
+ final_actions.append( ('vlan', action[0]) )
+ previous_vlan = action[0]
+ if self.pmp_with_same_vlan and action_number:
+ return -1, "Can not interconnect different vlan tags in a network when flag 'of_controller_nets_with_same_vlan' is True."
+ action_number += 1
+ final_actions.append( ('out', action[1]) )
+ flow_broadcast['actions'] = final_actions
+
+ if self._check_flow_already_present(flow_broadcast, new_flows) >= 0:
+ self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast))
+ continue
+
+ new_flows.append(flow_broadcast)
+
+ #UNIFY openflow rules with the same input port and vlan and the same output actions
+ #These flows differ at the dst_mac; and they are unified by not filtering by dst_mac
+ #this can happen if there is only two ports. It is converted to a point to point connection
+ flow_dict={} # use as key vlan_id+ingress_port and as value the list of flows matching these values
+ for flow in new_flows:
+ key = str(flow.get("vlan_id"))+":"+flow["ingress_port"]
+ if key in flow_dict:
+ flow_dict[key].append(flow)
+ else:
+ flow_dict[key]=[ flow ]
+ new_flows2=[]
+ for flow_list in flow_dict.values():
+ convert2ptp=False
+ if len (flow_list)>=2:
+ convert2ptp=True
+ for f in flow_list:
+ if f['actions'] != flow_list[0]['actions']:
+ convert2ptp=False
+ break
+ if convert2ptp: # add only one unified rule without dst_mac
+ self.logger.debug("Convert flow rules to NON mac dst_address " + str(flow_list) )
+ flow_list[0].pop('dst_mac')
+ flow_list[0]["priority"] -= 5
+ new_flows2.append(flow_list[0])
+ else: # add all the rules
+ new_flows2 += flow_list
+ return 0, new_flows2
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# PYTHON_ARGCOMPLETE_OK
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is a client for managing the openvim server.
+Useful for human CLI management of items at openvim
+'''
+__author__="Alfonso Tierno, Gerardo Garcia"
+__date__ ="$26-nov-2014 19:09:29$"
+__version__="0.4.0-r443"
+version_date="Nov 2015"
+name="openvim"
+
+from argcomplete.completers import FilesCompleter
+import os
+import argparse
+import argcomplete
+import requests
+import json
+import yaml
+from jsonschema import validate as js_v, exceptions as js_e
+
+class ArgumentParserError(Exception): pass
+
+class ThrowingArgumentParser(argparse.ArgumentParser):
+ def error(self, message):
+ print "Error: %s" %message
+ print
+ self.print_usage()
+ #self.print_help()
+ print
+ print "Type 'openvim -h' for help"
+ raise ArgumentParserError
+
+global vim_config
+config_items=("HOST", "PORT", "ADMIN_PORT", "TENANT")
+show_at_verbose1=('status', 'created', 'path', 'last_error','description','hostId','progress',
+ 'ram','vcpus','type','shared','admin_state_up', 'enabled', 'ip_name')
+
+#template for creating new items
+template={
+ "port":{
+ "${}":[
+ "${name} provide a port name",
+ "${net_id} provide the network uuid (null):",
+ "${vlan} provide the vlan if any (null):",
+ "${port} provide the attached switch port (Te0/47)",
+ "${mac} provide the mac of external device if known (null):"
+ ],
+ "port":{
+ "name": "${name}",
+ "network_id": "${net_id null}",
+ "type": "external",
+ "binding:vlan": "${vlan null-int}",
+ "binding:switch_port": "${port}",
+ "mac_address": "${mac null}"
+ },
+ },
+ "network":{
+ "${}":[
+ "${name} provide a network name",
+ "${type} provide a type: bridge_data,bridge_man,data,ptp (data)",
+ "${shared} external network: true,false (true)",
+ "${phy} conected to: bridge:name,macvtap:iface,default (null)"
+ ],
+ "network":{
+ "name": "${name}",
+ "type": "${type}",
+ "provider:physical": "${phy null}",
+ "shared": "${shared bool}"
+ }
+ },
+ "host": {
+ "${}":[
+ "${name} host name",
+ "${user} host user (user)",
+ "${ip_name} host access IP or name (${name})",
+ "${description} host description (${name})"
+ ],
+
+ "host":{
+ "name": "${name}",
+ "user": "${user}",
+ "ip_name": "${ip_name}",
+ "description": "${description}"
+ }
+ },
+ "flavor":{
+ "${}":[
+ "${name} flavor name",
+ "${description} flavor description (${name})",
+ "${processor_ranking} processor ranking (100)",
+ "${memory} memory in GB (2)",
+ "${threads} threads needed (2)"
+ ],
+ "flavor":{
+ "name":"${name}",
+ "description":"${description}",
+ "extended":{
+ "processor_ranking":"${processor_ranking int}",
+ "numas":[
+ {
+ "memory":"${memory int}",
+ "threads":"${threads int}"
+ }
+ ]
+ }
+ }
+ },
+ "tenant":{
+ "${}":[
+ "${name} tenant name",
+ "${description} tenant description (${name})"
+ ],
+ "tenant":{
+ "name": "${name}",
+ "description": "${description}"
+ }
+ },
+ "image":{
+ "${}":[
+ "${name} image name",
+ "${path} image path (/path/to/shared/compute/host/folder/image.qcow2)",
+ "${description} image description (${name})"
+ ],
+ "image":{
+ "name":"${name}",
+ "description":"${description}",
+ "path":"${path}"
+ }
+ },
+ "server":{
+ "${}":[
+ "${name} provide a name (VM)",
+ "${description} provide a description (${name})",
+ "${image_id} provide image_id uuid",
+ "${flavor_id} provide flavor_id uuid",
+ "${network0} provide a bridge network id; enter='default' (00000000-0000-0000-0000-000000000000)",
+ "${network1} provide a bridge network id; enter='virbrMan' (60f5227e-195f-11e4-836d-52540030594e)"
+ ],
+ "server":{
+ "networks":[
+ {
+ "name":"mgmt0",
+ "vpci": "0000:00:0a.0",
+ "uuid":"${network0}"
+ },
+ {
+ "name":"ge0",
+ "vpci": "0000:00:0b.0",
+ "uuid":"${network1}"
+ }
+ ],
+ "name":"${name}",
+ "description":"${description}",
+ "imageRef": "${image_id}",
+ "flavorRef": "${flavor_id}"
+ }
+ }
+}
+
+def check_configuration(*exception ):
+ '''
+ Check that the configuration variables are present.
+ exception can contain variables that are not tested, normally because is not used for the command
+ '''
+ #print exception
+ for item in config_items:
+ if item not in exception and vim_config[item]==None:
+ print "OPENVIM_"+item+" variable not defined. Try '" + name + " config file_cfg' or 'export OPENVIM_"+item+"=something'"
+ exit(-401); #HTTP_Unauthorized
+
+def check_valid_uuid(uuid):
+ '''
+ Determines if the param uuid is a well formed uuid. Otherwise it is consider a name
+ '''
+ id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
+ try:
+ js_v(uuid, id_schema)
+ return True
+ except js_e.ValidationError:
+ return False
+
+def _print_verbose(row_data, element, verbose=0):
+ #print row_data
+ data = row_data[element]
+ if verbose==1 or verbose==2:
+ data2= dict((k,v) for (k,v) in data.iteritems() if k in show_at_verbose1 and v!=None)
+ if 'image' in data and 'id' in data['image']:
+ data2['image'] = data['image']['id']
+ if 'flavor' in data and 'id' in data['flavor']:
+ data2['flavor'] = data['flavor']['id']
+ if verbose==2:
+ #TODO add more intems in a digest mode, extended, numas ...
+ pass
+ #if "numas" in data
+ #if "extended" in data
+ else:
+ data2= data
+ #print json.dumps(c2, indent=4)
+# keys = data.keys()
+# for key in keys:
+# if data[key]==None: del data[key]
+# elif key=='id' or key=='name' or key=='links': del data[key]
+
+ #print json.dumps(data2, indent=4)
+ data2={element: data2}
+ print yaml.safe_dump(data2, indent=4, default_flow_style=False)
+
+def vim_read(url):
+ '''
+ Send a GET http to VIM
+ '''
+ headers_req = {'content-type': 'application/json'}
+ try:
+ vim_response = requests.get(url, headers = headers_req)
+ if vim_response.status_code == 200:
+ #print vim_response.json()
+ #print json.dumps(vim_response.json(), indent=4)
+ content = vim_response.json()
+ return 1, content
+ #print http_content
+ else:
+ #print " Error. VIM response '%s': not possible to GET %s, error %s" % (vim_response.status_code, url, vim_response.text)
+ return -vim_response.status_code, vim_response.text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception GET at '"+url+"' " + str(e.message)
+
+def vim_delete(url):
+ '''
+ Send a DELETE http to VIM
+ '''
+ headers_req = {'content-type': 'application/json'}
+ try:
+ vim_response = requests.delete(url, headers = headers_req)
+ if vim_response.status_code != 200:
+ #print " Error. VIM response '%s': not possible to DELETE %s, error %s" % (vim_response.status_code, url, vim_response.text)
+ return -vim_response.status_code, vim_response.text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception DELETE at '"+url+"' " + str(e.message)
+ return 1, vim_response.json()
+
+def vim_action(url, payload):
+ '''
+ Send a POST http to VIM
+ '''
+ headers_req = {'content-type': 'application/json'}
+ try:
+ vim_response = requests.post(url, data=json.dumps(payload), headers = headers_req)
+ if vim_response.status_code != 200:
+ #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text)
+ return -vim_response.status_code, vim_response.text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception POST at '"+url+"' " + str(e.message)
+ return 1, vim_response.json()
+
+def vim_edit(url, payload):
+ headers_req = {'content-type': 'application/json'}
+ try:
+ vim_response = requests.put(url, data=json.dumps(payload), headers = headers_req)
+ if vim_response.status_code != 200:
+ #print " Error. VIM response '%s': not possible to PUT %s, error %s" % (vim_response.status_code, url, vim_response.text)
+ return -vim_response.status_code, vim_response.text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception PUT at '"+url+"' " + str(e.message)
+ return 1, vim_response.json()
+
+def vim_create(url, payload):
+ headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+ #print str(payload)
+ try:
+ vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req)
+ if vim_response.status_code != 200:
+ #print " Error. VIM response '%s': not possible to POST %s, error %s" % (vim_response.status_code, url, vim_response.text)
+ return -vim_response.status_code, vim_response.text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception POST at '"+url+"' " + str(e.message)
+ return 1, vim_response.json()
+
+def parse_yaml_json(text, file_name=None):
+ parser_json=None
+ if file_name:
+ if file_name[-5:]=='.yaml' or file_name[-4:]=='.yml':
+ parser_json = False
+ elif file_name[-5:]=='.json':
+ parser_json = True
+ if parser_json==None:
+ parser_json=True if '\t' in text else False
+
+ if parser_json: #try parse in json format, because yaml does not admit tabs
+ try:
+ data = json.loads(text)
+ return 0, data
+ except Exception as e:
+ return -1, "Error json format: " + str(e)
+ #try to parse in yaml format
+ try:
+ data = yaml.load(text)
+ return 0, data
+ except yaml.YAMLError as exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at line:%s column:%s" % (mark.line+1, mark.column+1)
+ return -1, " Error yaml format error" + error_pos
+
+def change_string(text, var_list):
+ '''
+ See change_var_recursively help
+ Used for changed any '${var format}' content inside the 'text' string
+ into the corresponding value at 'var_list'[var]
+ 'format' is optional, string by default. Contain a - separated formats,ej, int-null
+ '''
+ end=0
+ type_=None
+ while True:
+ ini = text.find("${", end)
+ if ini<0: return text
+ end = text.find("}", ini)
+ if end<0: return text
+ end+=1
+
+ var = text[ini:end]
+ if ' ' in var:
+ kk=var.split(" ")
+ var=kk[0]+"}"
+ type_=kk[-1][:-1]
+ var = var_list.get(var, None)
+ if var==None: return text
+
+ text = text[:ini] + var + text[end:]
+ if type_ != None:
+ if 'null' in type_ and text=="null":
+ return None
+ if 'int' in type_ : #and text.isnumeric():
+ return int(text)
+ if 'bool' in type_ : #and text.isnumeric():
+ if text.lower()=="true": return True
+ elif text.lower()=="false": return False
+ else:
+ print "input boolean paramter must be 'true' or 'false'"
+ exit(1)
+ return text
+
+def chage_var_recursively(data, var_list):
+ '''
+ Check recursively the content of 'data', and look for "*${*}*" variables and changes
+ It assumes that this variables are not in the key of dictionary,
+ The overall target is having json/yaml templates with this variables in the text.
+ The user is asked for a value that is replaced wherever appears
+ Attributes:
+ 'data': dictionary, or list. None or empty is considered valid
+ 'var_list': dictionary (name:change) pairs
+ Return:
+ None, data is modified
+ '''
+
+ if type(data) is dict:
+ for k in data.keys():
+ if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
+ chage_var_recursively(data[k], var_list)
+ elif type(data[k]) is str:
+ data[k] = change_string(data[k], var_list)
+ if type(data) is list:
+ for k in range(0,len(data)):
+ if type(data[k]) is dict or type(data[k]) is list:
+ chage_var_recursively(data[k], var_list)
+ elif type(data[k]) is str:
+ data[k] = change_string(data[k], var_list)
+
+def change_var(data, default_values={}):
+ ''' Look for a text "${}" key at 'data' that indicates that this json contains
+ variables that must be ask for values to the user and changes in the text of
+ the dictionary
+ 'default_values' contain a dictionary of values that must be used and not be asked
+ Useful for creating templates
+ "${}" entry contain a list with the text:
+ ${var-name} prompt text to show user (Default value to allocate if user type CR)
+ '''
+ if type(data) is not dict:
+ return -1, "Format error, not a object (dictionary)"
+ if "${}" not in data:
+ return 0, data
+
+ var_list={}
+ for var in data["${}"]:
+ r = var.find("}",) + 1
+ if r<=2 or var[:2] != '${':
+ return -1, "Format error at '${}':" + var
+ #change variables inside description text
+ if "${" in var[r:]:
+ var = var[:r] + change_string(var[r:], var_list)
+ d_start = var.rfind("(",) + 1
+ d_end = var.rfind(")",)
+ if d_start>0 and d_end>=d_start:
+ default = var[d_start:d_end]
+ else: default=None
+ if var[2:r-1] in default_values:
+ var_list[ var[:r] ] = default_values[ var[2:r-1] ]
+ continue
+ v = raw_input(var[r:] + "? ")
+ if v=="":
+ if default != None:
+ v = default
+ else:
+ v = raw_input(" empty string? try again: ")
+ var_list[ var[:r] ] = str(v)
+ del data["${}"]
+ chage_var_recursively(data, var_list)
+ return 0, data
+
+def load_file(file_, parse=False):
+ try:
+ f = open(file_, 'r')
+ read_data = f.read()
+ f.close()
+ if not parse:
+ return 0, read_data
+ except IOError, e:
+ return -1, " Error opening file '" + file_ + "': " + e.args[1]
+ return parse_yaml_json(read_data, file_)
+
+def load_file_or_yaml(content):
+ '''
+ 'content' can be or a yaml/json file or a text containing a yaml/json text format
+ This function autodetect, trying to load and parse the filename 'content',
+ if fails trying to parse the 'content' as text
+ Returns the dictionary once parsed, or print an error and finish the program
+ '''
+ r,payload = load_file(content, parse=True)
+ if r<0:
+ if r==-1 and "{" in content or ":" in content:
+ #try to parse directly
+ r,payload = parse_yaml_json(content)
+ if r<0:
+ print payload
+ exit (-1)
+ else:
+ print payload
+ exit (-1)
+ return payload
+
+def config(args):
+ #print "config-list",args
+ if args.file != None:
+ try:
+ f = open(args.file, 'r')
+ read_data = f.read()
+ f.close()
+ except IOError, e:
+ print " Error opening file '" + args.file + "': " + e.args[1]
+ return -1
+ try:
+ data = yaml.load(read_data)
+ #print data
+ if "http_host" in data:
+ print " export OPENVIM_HOST="+ data["http_host"]
+ if "http_port" in data:
+ print " export OPENVIM_PORT="+ str(data["http_port"])
+ #vim_config[item] = data["OPENVIM_"+item] #TODO find the way to change envioronment
+ #os.setenv('OPENVIM_'+item, vim_config[item])
+ if "http_admin_port" in data:
+ print " export OPENVIM_ADMIN_PORT="+ str(data["http_admin_port"])
+ if "tenant_id" in data:
+ print " export OPENVIM_TENANT="+ data["tenant_id"]
+ return 0
+ except yaml.YAMLError, exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ print " Error yaml/json format error at '"+ args.file +"'"+error_pos
+ return -1
+ print args.file
+ print "OPENVIM_HOST: %s" %vim_config["HOST"]
+ print "OPENVIM_PORT: %s" %vim_config["PORT"]
+ print "OPENVIM_ADMIN_PORT: %s" %vim_config["ADMIN_PORT"]
+ print "OPENVIM_TENANT: %s" %vim_config["TENANT"]
+ return 0
+
+def element_new(args):
+ #print args
+ tenant=""
+ if args.element in ('flavors','images','servers'):
+ check_configuration( "ADMIN_PORT" )
+ tenant="/"+vim_config["TENANT"]
+ else:
+ check_configuration("ADMIN_PORT", "TENANT")
+ tenant=""
+
+ default_values={}
+ if args.name != None:
+ default_values["name"] = args.name
+ if "description" in args and args.description != None:
+ default_values["description"] = args.description
+ if "path" in args and args.path != None:
+ default_values["path"] = args.path
+ if args.file==None:
+ payload= template[args.element[:-1] ]
+ payload = yaml.load(str(payload)) #with this trick we make a completely copy of the data, so to not modified the original one
+ else:
+ payload=load_file_or_yaml(args.file)
+ r,c= change_var(payload, default_values)
+ if r<0:
+ print "Template error", c
+ return -1
+ payload=c
+ #print payload
+ if args.element[:-1] not in payload:
+ payload = {args.element[:-1]: payload }
+ item = payload[args.element[:-1]]
+
+ url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
+ if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
+ url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
+ else:
+ url_admin = None
+
+ if args.name != None:
+ item["name"] = args.name
+ if "description" in args and args.description != None:
+ item["description"] = args.description
+ if "path" in args and args.path != None:
+ item["path"] = args.path
+
+
+ r,c = vim_create(url, payload)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ url=url_admin
+ r,c = vim_create(url, payload)
+ if r<0:
+ print c
+ return -r
+ else:
+ #print c
+ item=c[ args.element[:-1] ]
+ uuid=item.get('id', None)
+ if uuid is None:
+ uuid=item.get('uuid', 'uuid-not-found?')
+ name = item.get('name', '')
+ print " " + uuid +" "+ name.ljust(20) + " Created"
+ for e in ('warning', 'error', 'last_error'):
+ if e in item: print e + "\n" + item[e]
+ if args.verbose!=None:
+ _print_verbose(c, args.element[:-1], args.verbose)
+ return 0
+
+def element_action(args):
+ filter_qs = ""
+ tenant=""
+ if args.element in ('flavors','images','servers'):
+ check_configuration( "ADMIN_PORT")
+ tenant="/"+vim_config["TENANT"]
+ else:
+ check_configuration("ADMIN_PORT", "TENANT")
+ tenant=""
+ url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
+ if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
+ url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
+ else:
+ url_admin = None
+
+ if args.filter:
+ filter_qs += "?" + args.filter
+ if args.name!=None:
+ if check_valid_uuid(args.name):
+ if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
+ else: filter_qs += "?" + "id=" + str(args.name)
+ else:
+ if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
+ else: filter_qs += "?" + "name=" + str(args.name)
+
+
+ r,c = vim_read(url + filter_qs)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ r,c = vim_read(url_admin + filter_qs)
+ if r<0:
+ print "Error:", c
+ return -r
+ if args.action=='createImage':
+ payload={ args.action: {"name":args.imageName} }
+ if args.description != None:
+ payload[args.action]["description"]=args.description
+ if args.path != None:
+ payload[args.action]["path"]=args.path
+ else:
+ payload={ args.action: None}
+ #print json.dumps(c, indent=4)
+ item_list = c[ args.element ]
+ if len(item_list)==0 and args.name != None:
+ print " Not found " + args.element + " " + args.name
+ return 404 #HTTP_Not_Found
+ result = 0
+ for item in item_list:
+ uuid=item.get('id', None)
+ if uuid is None:
+ uuid=item.get('uuid', None)
+ if uuid is None:
+ print "Id not found"
+ continue
+ name = item.get('name', '')
+ if not args.force:
+ r = raw_input(" Action over " + args.element + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ pass
+ else:
+ continue
+ r,c = vim_action(url + "/" + uuid + "/action", payload)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ url=url_admin
+ r,c = vim_action(url + "/" + uuid + "/action", payload)
+ if r<0:
+ print " " + uuid +" "+ name.ljust(20) + " " + c
+ result = -r
+ else:
+ if args.action == "createImage": #response contain an {image: {...} }, not a {server: {...} }.
+ print " " + c["image"]["id"] +" "+ c["image"]["name"].ljust(20)
+ args.element="images"
+ else:
+ print " " + uuid +" "+ name.ljust(20) + " "+ args.action
+ if "verbose" in args and args.verbose!=None:
+ _print_verbose(c, args.element[:-1], args.verbose)
+ return result
+
+def element_edit(args):
+ filter_qs = ""
+ tenant=""
+ if args.element in ('flavors','images','servers'):
+ check_configuration( "ADMIN_PORT")
+ tenant="/"+vim_config["TENANT"]
+ else:
+ check_configuration("ADMIN_PORT", "TENANT")
+ tenant=""
+
+ url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
+ if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
+ url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
+ else:
+ url_admin = None
+
+ if args.filter:
+ filter_qs += "?" + args.filter
+ if args.name!=None:
+ if check_valid_uuid(args.name):
+ if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
+ else: filter_qs += "?" + "id=" + str(args.name)
+ else:
+ if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
+ else: filter_qs += "?" + "name=" + str(args.name)
+
+
+ r,c = vim_read(url + filter_qs)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ r,c = vim_read(url_admin + filter_qs)
+ if r<0:
+ print "Error:", c
+ return -r
+
+ payload=load_file_or_yaml(args.file)
+ r2,c2= change_var(payload)
+ if r2<0:
+ print "Template error", c2
+ return -1
+ payload=c2
+ if args.element[:-1] not in payload:
+ payload = {args.element[:-1]: payload }
+
+ #print json.dumps(c, indent=4)
+ item_list = c[ args.element ]
+ if len(item_list)==0 and args.name != None:
+ print " Not found " + args.element + " " + args.name
+ return 404 #HTTP_Not_Found
+ result = 0
+ for item in item_list:
+ uuid=item.get('id', None)
+ if uuid is None:
+ uuid=item.get('uuid', None)
+ if uuid is None:
+ print "Id not found"
+ continue
+ name = item.get('name', '')
+ if not args.force or (args.name==None and args.filer==None):
+ r = raw_input(" Edit " + args.element + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ pass
+ else:
+ continue
+ r,c = vim_edit(url + "/" + uuid, payload)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ url=url_admin
+ r,c = vim_edit(url + "/" + uuid, payload)
+ if r<0:
+ print " " + uuid +" "+ name.ljust(20) + " " + c
+ result = -r
+ else:
+ print " " + uuid +" "+ name.ljust(20) + " edited"
+ if "verbose" in args and args.verbose!=None:
+ _print_verbose(c, args.element[:-1], args.verbose)
+ return result
+
+def element_action_edit(args):
+ #print args
+ if args.element=='ports':
+ if args.action=='attach':
+ args.file='network_id: ' + args.network_id
+ else: #args.action=='detach'
+ args.file='network_id: null'
+ if args.action=='up':
+ args.file='admin_state_up: true'
+ if args.action=='down':
+ args.file='admin_state_up: false'
+ return element_edit(args)
+
+def element_delete(args):
+ filter_qs = ""
+ tenant=""
+ if args.element in ('flavors','images','servers'):
+ check_configuration("ADMIN_PORT" )
+ tenant="/"+vim_config["TENANT"]
+ else:
+ check_configuration("ADMIN_PORT", "TENANT")
+ tenant=""
+
+ url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
+ if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
+ url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
+ else:
+ url_admin = None
+
+ if args.filter:
+ filter_qs += "?" + args.filter
+ if args.name!=None:
+ if check_valid_uuid(args.name):
+ if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
+ else: filter_qs += "?" + "id=" + str(args.name)
+ else:
+ if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
+ else: filter_qs += "?" + "name=" + str(args.name)
+
+
+ r,c = vim_read(url + filter_qs)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ r,c = vim_read(url_admin + filter_qs)
+ if r<0:
+ print "Error:", c
+ return -r
+
+ #print json.dumps(c, indent=4)
+ item_list = c[ args.element ]
+ if len(item_list)==0 and args.name != None:
+ print " Not found " + args.element + " " + args.name
+ return 404 #HTTP_Not_Found
+ result = 0
+ for item in item_list:
+ uuid=item.get('id', None)
+ if uuid is None:
+ uuid=item.get('uuid', None)
+ if uuid is None:
+ print "Id not found"
+ result = -500
+ continue
+ name = item.get('name', '')
+ if not args.force:
+ r = raw_input(" Delete " + args.element + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ pass
+ else:
+ continue
+ r,c = vim_delete(url + "/" + uuid)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ url=url_admin
+ r,c = vim_delete(url + "/" + uuid)
+ if r<0:
+ print " " + uuid +" "+ name.ljust(20) + " " + c
+ result = -r
+ else:
+ print " " + uuid +" "+ name.ljust(20) + " deleted"
+ return result
+
+def element_list(args):
+ #print "element_list", args
+ filter_qs = ""
+ tenant=""
+ if args.element in ('flavors','images','servers'):
+ check_configuration( "ADMIN_PORT" )
+ tenant="/"+vim_config["TENANT"]
+ else:
+ check_configuration( "ADMIN_PORT", "TENANT" )
+ tenant=""
+ #if args.name:
+ # what += "/" + args.name
+ url = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["PORT"], tenant, args.element)
+ if "ADMIN_PORT" in vim_config and vim_config["ADMIN_PORT"]!=None:
+ url_admin = "http://%s:%s/openvim%s/%s" %(vim_config["HOST"], vim_config["ADMIN_PORT"], tenant, args.element)
+ else:
+ url_admin = None
+ #print " get", what, " >>>>>>>> ",
+ if args.filter:
+ filter_qs += "?" + args.filter
+ if args.name!=None:
+ if check_valid_uuid(args.name):
+ if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(args.name)
+ else: filter_qs += "?" + "id=" + str(args.name)
+ else:
+ if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(args.name)
+ else: filter_qs += "?" + "name=" + str(args.name)
+
+
+ r,c = vim_read(url + filter_qs)
+ if r==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ r,c = vim_read(url_admin + filter_qs)
+ if r<0:
+ print "Error:", c
+ return -r
+ #print json.dumps(c, indent=4)
+ result = 0
+ item_list = c[ args.element ]
+ verbose=0
+ #if args.name!=None and len(item_list)==1:
+ # verbose+=1
+ if args.verbose!=None:
+ verbose += args.verbose
+ for item in item_list:
+ extra=""
+ if args.element=="servers" or args.element=="networks": extra = " "+item['status']
+ if args.element in ("hosts","networks","ports") and not item['admin_state_up']: extra += " admin_state_up=false"
+ print item['id']+" "+item['name'].ljust(20) + extra
+ if verbose>0:
+ r2,c2 = vim_read(url + "/"+ item['id'])
+ if r2==-401 and url_admin!=None and url != url_admin: #Unauthorized, try with admin privileges
+ url=url_admin
+ r2,c2 = vim_read(url + "/"+ item['id'])
+ if r2>0:
+ _print_verbose(c2, args.element[:-1], verbose)
+
+ return result
+
+def openflow_action(args):
+ if args.action=='port-list':
+ url = "http://%s:%s/openvim/networks/openflow/ports" %(vim_config["HOST"], vim_config["PORT"])
+ r,c = vim_read(url)
+ elif args.action=='rules-list' or args.action=='reinstall':
+ PORT = vim_config["PORT"]
+ if args.action=='reinstall':
+ if "ADMIN_PORT" not in vim_config:
+ print "OPENVIM_ADMIN_PORT variable not defined"
+ return 404 #HTTP_Not_Found
+ PORT = vim_config["ADMIN_PORT"]
+ if args.name!=None:
+ url = "http://%s:%s/openvim/networks" %(vim_config["HOST"], PORT)
+ if check_valid_uuid(args.name):
+ url += "?id=" + str(args.name)
+ else:
+ url += "?name=" + str(args.name)
+ r,c = vim_read(url)
+ if r<0:
+ print "Error:", c
+ return -r
+ if len (c["networks"]) == 0:
+ print " Network not found"
+ return 404 #HTTP_Not_Found
+ if len (c["networks"]) > 1:
+ print " More than one net with this name found. Use uuid instead of name to concretize"
+ return 404 #HTTP_Not_Found
+ network = c["networks"][0]["id"]
+ else:
+ network="all"
+ url = "http://%s:%s/openvim/networks/%s/openflow" %(vim_config["HOST"], PORT, network)
+ if args.action=='reinstall':
+ r,c = vim_edit(url, None)
+ else:
+ r,c = vim_read(url)
+ elif args.action=='clear-all':
+ if "ADMIN_PORT" not in vim_config:
+ print "OPENVIM_ADMIN_PORT variable not defined"
+ return 401 # HTTP_Unauthorized
+ url = "http://%s:%s/openvim/networks/openflow/clear" %(vim_config["HOST"], vim_config["ADMIN_PORT"])
+ r,c = vim_delete(url)
+ else:
+ return 400 #HTTP_Bad_Request
+ if r<0:
+ print "Error:", c
+ return -r
+ else:
+ print yaml.safe_dump(c, indent=4, default_flow_style=False)
+ return 0
+
+if __name__=="__main__":
+
+ item_dict={'vm':'servers','host':'hosts','tenant':'tenants','image':'images','flavor':'flavors','net':'networks','port':'ports'}
+
+ vim_config={}
+ vim_config["HOST"] = os.getenv('OPENVIM_HOST', 'localhost')
+ vim_config["PORT"] = os.getenv('OPENVIM_PORT', '9080')
+ vim_config["ADMIN_PORT"] = os.getenv('OPENVIM_ADMIN_PORT', '9085')
+ vim_config["TENANT"] = os.getenv('OPENVIM_TENANT', None)
+
+
+ main_parser = ThrowingArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
+ #main_parser = argparse.ArgumentParser(description='User program to interact with OPENVIM-SERVER (openvimd)')
+ main_parser.add_argument('--version', action='version', version='%(prog)s ' + __version__ + ' '+version_date)
+
+ subparsers = main_parser.add_subparsers(help='commands')
+
+ config_parser = subparsers.add_parser('config', help="prints configuration values")
+ config_parser.add_argument("file", nargs='?', help="configuration file to extract the configuration").completer = FilesCompleter
+ config_parser.set_defaults(func=config)
+ #HELP
+ for item in item_dict:
+ #LIST
+ element_list_parser = subparsers.add_parser(item+'-list', help="lists information about "+item_dict[item])
+ element_list_parser.add_argument("name", nargs='?', help="name or ID of the " + item)
+ element_list_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ element_list_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
+ element_list_parser.set_defaults(func=element_list, element=item_dict[item])
+ #NEW
+ if item=='host':
+ element_new_parser = subparsers.add_parser(item+'-add', help="adds a new compute node")
+ else:
+ element_new_parser = subparsers.add_parser(item+'-create', help="creates a new "+item_dict[item][:-1])
+ element_new_parser.add_argument("file", nargs='?', help="json/yaml text or file with content").completer = FilesCompleter
+ element_new_parser.add_argument("--name", action="store", help="Use this name")
+ if item!="network":
+ element_new_parser.add_argument("--description", action="store", help="Use this descrition")
+ if item=="image":
+ element_new_parser.add_argument("--path", action="store", help="Use this path")
+ element_new_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
+ element_new_parser.set_defaults(func=element_new, element=item_dict[item])
+ #DELETE
+ if item=='host':
+ element_del_parser = subparsers.add_parser(item+'-remove', help="removes a compute node")
+ else:
+ element_del_parser = subparsers.add_parser(item+'-delete', help="deletes one or several "+item_dict[item])
+ element_del_parser.add_argument("name", nargs='?', help="name or ID of the "+item+", if missing means all")
+ element_del_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ element_del_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ element_del_parser.set_defaults(func=element_delete, element=item_dict[item])
+ #EDIT
+ element_edit_parser = subparsers.add_parser(item+'-edit', help="edits one or several "+item_dict[item])
+ element_edit_parser.add_argument("name", help="name or ID of the "+item+"")
+ element_edit_parser.add_argument("file", help="json/yaml text or file with the changes").completer = FilesCompleter
+ element_edit_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ element_edit_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ element_edit_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
+ element_edit_parser.set_defaults(func=element_edit, element=item_dict[item])
+ #ACTION
+ if item=='vm':
+ for item2 in ('shutdown', 'start', 'rebuild', 'reboot'):
+ vm_action_parser = subparsers.add_parser("vm-"+item2, help="performs this action over the virtual machine")
+ vm_action_parser.add_argument("name", nargs='?', help="name or ID of the server, if missing means all")
+ vm_action_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ vm_action_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ vm_action_parser.set_defaults(func=element_action, element="servers", action=item2 )
+ vm_action_image_parser = subparsers.add_parser("vm-createImage", help="creates a snapshot of the virtual machine disk into a new image")
+ vm_action_image_parser.add_argument("name", help="name or ID of the server")
+ vm_action_image_parser.add_argument("imageName", help="image name")
+ vm_action_image_parser.add_argument("--description", action="store", help="Provide a new image description")
+ vm_action_image_parser.add_argument("--path", action="store", help="Provide a new image complete path")
+ vm_action_image_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ vm_action_image_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ vm_action_image_parser.add_argument('--verbose', '-v', action='count', help="increment the verbosity")
+ vm_action_image_parser.set_defaults(func=element_action, element="servers", action="createImage" )
+ #ACTION that are implemented with EDITION
+ if item=='port':
+ port_action_attach_parser = subparsers.add_parser("port-attach", help="connects a port to a network")
+ port_action_attach_parser.add_argument("name", help="name or ID of the port")
+ port_action_attach_parser.add_argument("network_id", help="ID of the network")
+ port_action_attach_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ port_action_attach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ port_action_attach_parser.set_defaults(func=element_action_edit, element="ports", action="attach")
+
+ port_action_detach_parser = subparsers.add_parser("port-detach", help="removes a port from a network")
+ port_action_detach_parser.add_argument("name", help="name or ID of the port")
+ port_action_detach_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ port_action_detach_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ port_action_detach_parser.set_defaults(func=element_action_edit, element="ports", action="dettach")
+
+ if item=='net' or item=='host':
+ nethost_action_up_parser = subparsers.add_parser(item+"-up", help="puts admin_state_up of "+item_dict[item][:-1]+" to true")
+ nethost_action_up_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
+ nethost_action_up_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ nethost_action_up_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ nethost_action_up_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="up")
+
+ nethost_action_down_parser = subparsers.add_parser(item+"-down", help="puts admin_state_up of "+item_dict[item][:-1]+" to false")
+ nethost_action_down_parser.add_argument("name", help="name or ID of the "+item_dict[item][:-1])
+ nethost_action_down_parser.add_argument("-F","--filter", action="store", help="filter query string")
+ nethost_action_down_parser.add_argument("-f","--force", action="store_true", help="do not prompt for confirmation")
+ nethost_action_down_parser.set_defaults(func=element_action_edit, element=item_dict[item], action="down")
+
+ #openflow rules
+ openflow_list_action = subparsers.add_parser("openflow-port-list", help="list openflow switch ports name")
+ openflow_list_action.set_defaults(func=openflow_action, action="port-list")
+
+ openflow_list_action = subparsers.add_parser("openflow-clear-all", help="removes all openflow rules")
+ openflow_list_action.set_defaults(func=openflow_action, action="clear-all")
+
+ openflow_list_action = subparsers.add_parser("openflow-net-reinstall", help="reinstall the openflow rules for a network")
+ openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
+ openflow_list_action.set_defaults(func=openflow_action, action="reinstall")
+
+ openflow_list_action = subparsers.add_parser("openflow-net-list", help="list installed openflow rules for a network")
+ openflow_list_action.add_argument("name", nargs='?', help="network name, if missing all networks")
+ openflow_list_action.set_defaults(func=openflow_action, action="rules-list")
+
+ argcomplete.autocomplete(main_parser)
+
+ try:
+ args = main_parser.parse_args()
+ result = args.func(args)
+ if result == None:
+ result = 0
+ #for some reason it fails if call exit inside try instance. Need to call exit at the end !?
+ except (requests.exceptions.ConnectionError):
+ print "Connection error: not possible to contact OPENVIM-SERVER (openvimd)"
+ result = -2
+ except (KeyboardInterrupt):
+ print 'Exiting openVIM'
+ result = -3
+ except (SystemExit, ArgumentParserError):
+ result = -4
+
+ #print result
+ exit(result)
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+
+#Miscellaneous
+#Option to test openvim without the needed infrastructure, possible values are
+# "normal" by default, Openflow controller (OFC), switch and real host are needed
+# "test" Used for testing http API and database without connecting to host or to OFC
+# "host only" Used when neither OFC nor OF switch are provided.
+# Dataplane network connection must be done manually.
+# "OF only" Used for testing of new openflow controllers support. No real VM deployments will be done but
+# OFC will be used as in real mode
+# "development" Forced a cloud-type deployment, nomal memory instead of hugepages is used,
+# without cpu pinning, and using a bridge network instead of a real OFC dataplane networks.
+# The same 'development_bridge' (see below) is used for all dataplane networks
+mode: test
+
+#Openflow controller information
+of_controller: floodlight # Type of controller to be used.
+ # Valid controllers are 'opendaylight', 'floodlight' or <custom>
+#of_controller_module: module # Only needed for <custom>. Python module that implement
+ # this controller. By default a file with the name <custom>.py is used
+#of_<other>: value # Other parameters required by <custom> controller. Consumed by __init__
+of_user: user credentials # User credentials for the controller if needed
+of_password: passwd credentials # Password credentials for the controller if needed
+of_controller_ip: 127.0.0.1 # IP address where the Openflow controller is listening
+of_controller_port: 7070 # TCP port where the Openflow controller is listening (REST API server)
+of_controller_dpid: '00:01:02:03:04:05:06:07' # Openflow Switch identifier (put here the right number)
+
+#This option is used for those openflow switch that cannot deliver one packet to several output with different vlan tags
+#When set to true, it fails when trying to attach different vlan tagged ports to the same net
+of_controller_nets_with_same_vlan: false # (by default, true)
+
+#Server parameters
+http_host: localhost # IP address where openvim is listening (by default, localhost)
+http_port: 9080 # General port where openvim is listening (by default, 9080)
+http_admin_port: 9085 # Admin port where openvim is listening (when missing, no administration server is launched)
+
+#database parameters
+db_host: localhost # by default localhost
+db_user: vim # DB user
+db_passwd: vimpw # DB password
+db_name: vim_db # Name of the VIM DB
+
+#host paremeters
+image_path: "/opt/VNF/images" # Folder, same for every host, where the VNF images will be copied
+
+#testing parameters (used by ./test/test_openvim.py)
+tenant_id: fc7b43b6-6bfa-11e4-84d2-5254006d6777 # Default tenant identifier for testing
+
+#VLAN ranges used for the dataplane networks (ptp, data)
+#When a network is created an unused value in this range is used
+network_vlan_range_start: 3000
+network_vlan_range_end: 4000
+
+#host bridge interfaces for networks
+# Openvim cannot create bridge networks automatically, in the same way as other CMS do.
+# Bridge networks need to be pre-provisioned on each host and Openvim uses those pre-provisioned bridge networks.
+# Openvim assumes that the following bridge interfaces have been created on each host, appropriately associated to a physical port.
+# The following information needs to be provided:
+# - Name of the bridge (identical in all hosts)
+# - VLAN tag associated to each bridge interface
+# - The speed of the physical port in Gbps, where that bridge interface was created
+# For instance, next example assumes that 10 bridges have been created on each host
+# using vlans 2001 to 2010, associated to a 1Gbps physical port
+bridge_ifaces:
+ #name: [vlan, speed in Gbps]
+ virbrMan1: [2001, 1]
+ virbrMan2: [2002, 1]
+ virbrMan3: [2003, 1]
+ virbrMan4: [2004, 1]
+ virbrMan5: [2005, 1]
+ virbrMan6: [2006, 1]
+ virbrMan7: [2007, 1]
+ virbrMan8: [2008, 1]
+ virbrMan9: [2009, 1]
+ virbrMan10: [2010, 1]
+
+#Used only when 'mode' is at development'. Indicates which 'bridge_ifaces' is used for dataplane networks
+development_bridge: virbrMan10
+
+#DHCP SERVER PARAMETERS.
+#In case some of the previous 'bridge_ifaces' are connected to an EXTERNAL dhcp server, provide
+# the server parameters to allow openvim getting the allocated IP addresses of virtual machines
+# connected to the indicated 'bridge_ifaces' and or 'nets'. Openvim will connect to the dhcp server by ssh.
+#DHCP server must contain a shell script "./get_dhcp_lease.sh" that accept a mac address as parameter
+# and return empty or the allocated IP address. See an example at the end of the file ./openvim/dhcp_thread.py
+#COMMENT all lines in case you do not have a DHCP server in 'normal', 'development' or 'host only' modes.
+# For 'test' or 'OF only' modes you can leave then uncommented, because in these modes fake IP
+# address are generated instead of connecting with a real DHCP server.
+dhcp_server:
+ host: host-ip-or-name
+ #port: 22 #ssh port, by default 22
+ provider: isc-dhcp-server #dhcp-server type
+ user: user
+ #provide password, or key if needed
+ password: passwd
+ #key: ssh-access-key
+ #list of the previous bridge interfaces attached to this dhcp server
+ bridge_ifaces: [ virbrMan1, virbrMan2 ]
+ #list of the networks attached to this dhcp server
+ nets: [default]
+
+
+#logging parameters # DEBUG, INFO, WARNING, ERROR, CRITICAL
+log_level: ERROR
+log_level_db: DEBUG
+log_level_of: DEBUG
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is the main program of openvim, it reads the configuration
+and launches the rest of threads: http clients, openflow controller
+and host controllers
+'''
+
+__author__="Alfonso Tierno"
+__date__ ="$10-jul-2014 12:07:15$"
+__version__="0.4.6-r466"
+version_date="Jul 2016"
+database_version="0.7" #expected database schema version
+
+import httpserver
+from utils import auxiliary_functions as af
+import sys
+import getopt
+import time
+import vim_db
+import yaml
+import os
+from jsonschema import validate as js_v, exceptions as js_e
+import host_thread as ht
+import dhcp_thread as dt
+import openflow_thread as oft
+import threading
+from vim_schema import config_schema
+import logging
+import imp
+
+global config_dic
+global logger
+logger = logging.getLogger('vim')
+
+def load_configuration(configuration_file):
+ default_tokens ={'http_port':9080, 'http_host':'localhost',
+ 'of_controller_nets_with_same_vlan':True,
+ 'image_path':'/opt/VNF/images',
+ 'network_vlan_range_start':1000,
+ 'network_vlan_range_end': 4096,
+ 'log_level': "DEBUG",
+ 'log_level_db': "ERROR",
+ 'log_level_of': 'ERROR',
+ }
+ try:
+ #First load configuration from configuration file
+ #Check config file exists
+ if not os.path.isfile(configuration_file):
+ return (False, "Configuration file '"+configuration_file+"' does not exists")
+
+ #Read and parse file
+ (return_status, code) = af.read_file(configuration_file)
+ if not return_status:
+ return (return_status, "Error loading configuration file '"+configuration_file+"': "+code)
+ try:
+ config = yaml.load(code)
+ except yaml.YAMLError, exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ return (False, "Error loading configuration file '"+configuration_file+"'"+error_pos+": content format error: Failed to parse yaml format")
+
+
+ try:
+ js_v(config, config_schema)
+ except js_e.ValidationError, exc:
+ error_pos = ""
+ if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path))+"'"
+ return False, "Error loading configuration file '"+configuration_file+"'"+error_pos+": "+exc.message
+
+
+ #Check default values tokens
+ for k,v in default_tokens.items():
+ if k not in config: config[k]=v
+ #Check vlan ranges
+ if config["network_vlan_range_start"]+10 >= config["network_vlan_range_end"]:
+ return False, "Error invalid network_vlan_range less than 10 elements"
+
+ except Exception,e:
+ return (False, "Error loading configuration file '"+configuration_file+"': "+str(e))
+ return (True, config)
+
+def create_database_connection(config_dic):
+ db = vim_db.vim_db( (config_dic["network_vlan_range_start"],config_dic["network_vlan_range_end"]), config_dic['log_level_db'] );
+ if db.connect(config_dic['db_host'], config_dic['db_user'], config_dic['db_passwd'], config_dic['db_name']) == -1:
+ logger.error("Cannot connect to database %s at %s@%s", config_dic['db_name'], config_dic['db_user'], config_dic['db_host'])
+ exit(-1)
+ return db
+
+def usage():
+ print "Usage: ", sys.argv[0], "[options]"
+ print " -v|--version: prints current version"
+ print " -c|--config [configuration_file]: loads the configuration file (default: openvimd.cfg)"
+ print " -h|--help: shows this help"
+ print " -p|--port [port_number]: changes port number and overrides the port number in the configuration file (default: 9090)"
+ print " -P|--adminport [port_number]: changes admin port number and overrides the port number in the configuration file (default: 9095)"
+ return
+
+
+if __name__=="__main__":
+ #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
+ streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s"
+ logging.basicConfig(format=streamformat, level= logging.DEBUG)
+ logger.setLevel(logging.DEBUG)
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hvc:p:P:", ["config", "help", "version", "port", "adminport"])
+ except getopt.GetoptError, err:
+ # print help information and exit:
+ logger.error("%s. Type -h for help", err) # will print something like "option -a not recognized"
+ #usage()
+ sys.exit(-2)
+
+ port=None
+ port_admin = None
+ config_file = 'openvimd.cfg'
+
+ for o, a in opts:
+ if o in ("-v", "--version"):
+ print "openvimd version", __version__, version_date
+ print "(c) Copyright Telefonica"
+ sys.exit(0)
+ elif o in ("-h", "--help"):
+ usage()
+ sys.exit(0)
+ elif o in ("-c", "--config"):
+ config_file = a
+ elif o in ("-p", "--port"):
+ port = a
+ elif o in ("-P", "--adminport"):
+ port_admin = a
+ else:
+ assert False, "Unhandled option"
+
+
+ try:
+ #Load configuration file
+ r, config_dic = load_configuration(config_file)
+ #print config_dic
+ if not r:
+ logger.error(config_dic)
+ config_dic={}
+ exit(-1)
+ logging.basicConfig(level = getattr(logging, config_dic['log_level']))
+ logger.setLevel(getattr(logging, config_dic['log_level']))
+ #override parameters obtained by command line
+ if port is not None: config_dic['http_port'] = port
+ if port_admin is not None: config_dic['http_admin_port'] = port_admin
+
+ #check mode
+ if 'mode' not in config_dic:
+ config_dic['mode'] = 'normal'
+ #allow backward compatibility of test_mode option
+ if 'test_mode' in config_dic and config_dic['test_mode']==True:
+ config_dic['mode'] = 'test'
+ if config_dic['mode'] == 'development' and ( 'development_bridge' not in config_dic or config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None) ):
+ logger.error("'%s' is not a valid 'development_bridge', not one of the 'bridge_ifaces'", config_file)
+ exit(-1)
+
+ if config_dic['mode'] != 'normal':
+ print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
+ print "!! Warning, openvimd in TEST mode '%s'" % config_dic['mode']
+ print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
+ config_dic['version'] = __version__
+
+ #Connect to database
+ db_http = create_database_connection(config_dic)
+ r = db_http.get_db_version()
+ if r[0]<0:
+ logger.error("DATABASE is not a VIM one or it is a '0.0' version. Try to upgrade to version '%s' with './database_utils/migrate_vim_db.sh'", database_version)
+ exit(-1)
+ elif r[1]!=database_version:
+ logger.error("DATABASE wrong version '%s'. Try to upgrade/downgrade to version '%s' with './database_utils/migrate_vim_db.sh'", r[1], database_version)
+ exit(-1)
+ db_of = create_database_connection(config_dic)
+ db_lock= threading.Lock()
+ config_dic['db'] = db_of
+ config_dic['db_lock'] = db_lock
+
+ #precreate interfaces; [bridge:<host_bridge_name>, VLAN used at Host, uuid of network camping in this bridge, speed in Gbit/s
+ config_dic['dhcp_nets']=[]
+ config_dic['bridge_nets']=[]
+ for bridge,vlan_speed in config_dic["bridge_ifaces"].items():
+ #skip 'development_bridge'
+ if config_dic['mode'] == 'development' and config_dic['development_bridge'] == bridge:
+ continue
+ config_dic['bridge_nets'].append( [bridge, vlan_speed[0], vlan_speed[1], None] )
+ del config_dic["bridge_ifaces"]
+
+ #check if this bridge is already used (present at database) for a network)
+ used_bridge_nets=[]
+ for brnet in config_dic['bridge_nets']:
+ r,nets = db_of.get_table(SELECT=('uuid',), FROM='nets',WHERE={'provider': "bridge:"+brnet[0]})
+ if r>0:
+ brnet[3] = nets[0]['uuid']
+ used_bridge_nets.append(brnet[0])
+ if config_dic.get("dhcp_server"):
+ if brnet[0] in config_dic["dhcp_server"]["bridge_ifaces"]:
+ config_dic['dhcp_nets'].append(nets[0]['uuid'])
+ if len(used_bridge_nets) > 0 :
+ logger.info("found used bridge nets: " + ",".join(used_bridge_nets))
+ #get nets used by dhcp
+ if config_dic.get("dhcp_server"):
+ for net in config_dic["dhcp_server"].get("nets", () ):
+ r,nets = db_of.get_table(SELECT=('uuid',), FROM='nets',WHERE={'name': net})
+ if r>0:
+ config_dic['dhcp_nets'].append(nets[0]['uuid'])
+
+ # get host list from data base before starting threads
+ r,hosts = db_of.get_table(SELECT=('name','ip_name','user','uuid'), FROM='hosts', WHERE={'status':'ok'})
+ if r<0:
+ logger.error("Cannot get hosts from database %s", hosts)
+ exit(-1)
+ # create connector to the openflow controller
+ of_test_mode = False if config_dic['mode']=='normal' or config_dic['mode']=="OF only" else True
+
+ if of_test_mode:
+ OF_conn = oft.of_test_connector({"of_debug": config_dic['log_level_of']} )
+ else:
+ #load other parameters starting by of_ from config dict in a temporal dict
+ temp_dict={ "of_ip": config_dic['of_controller_ip'],
+ "of_port": config_dic['of_controller_port'],
+ "of_dpid": config_dic['of_controller_dpid'],
+ "of_debug": config_dic['log_level_of']
+ }
+ for k,v in config_dic.iteritems():
+ if type(k) is str and k[0:3]=="of_" and k[0:13] != "of_controller":
+ temp_dict[k]=v
+ if config_dic['of_controller']=='opendaylight':
+ module = "ODL"
+ elif "of_controller_module" in config_dic:
+ module = config_dic["of_controller_module"]
+ else:
+ module = config_dic['of_controller']
+ module_info=None
+ try:
+ module_info = imp.find_module(module)
+
+ OF_conn = imp.load_module("OF_conn", *module_info)
+ try:
+ OF_conn = OF_conn.OF_conn(temp_dict)
+ except Exception as e:
+ logger.error("Cannot open the Openflow controller '%s': %s", type(e).__name__, str(e))
+ if module_info and module_info[0]:
+ file.close(module_info[0])
+ exit(-1)
+ except (IOError, ImportError) as e:
+ if module_info and module_info[0]:
+ file.close(module_info[0])
+ logger.error("Cannot open openflow controller module '%s'; %s: %s; revise 'of_controller' field of configuration file.", module, type(e).__name__, str(e))
+ exit(-1)
+
+
+ #create openflow thread
+ thread = oft.openflow_thread(OF_conn, of_test=of_test_mode, db=db_of, db_lock=db_lock,
+ pmp_with_same_vlan=config_dic['of_controller_nets_with_same_vlan'],
+ debug=config_dic['log_level_of'])
+ r,c = thread.OF_connector.obtain_port_correspondence()
+ if r<0:
+ logger.error("Cannot get openflow information %s", c)
+ exit()
+ thread.start()
+ config_dic['of_thread'] = thread
+
+ #create dhcp_server thread
+ host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
+ dhcp_params = config_dic.get("dhcp_server")
+ if dhcp_params:
+ thread = dt.dhcp_thread(dhcp_params=dhcp_params, test=host_test_mode, dhcp_nets=config_dic["dhcp_nets"], db=db_of, db_lock=db_lock, debug=config_dic['log_level_of'])
+ thread.start()
+ config_dic['dhcp_thread'] = thread
+
+
+ #Create one thread for each host
+ host_test_mode = True if config_dic['mode']=='test' or config_dic['mode']=="OF only" else False
+ host_develop_mode = True if config_dic['mode']=='development' else False
+ host_develop_bridge_iface = config_dic.get('development_bridge', None)
+ config_dic['host_threads'] = {}
+ for host in hosts:
+ host['image_path'] = '/opt/VNF/images/openvim'
+ thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=db_of, db_lock=db_lock,
+ test=host_test_mode, image_path=config_dic['image_path'], version=config_dic['version'],
+ host_id=host['uuid'], develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface )
+ thread.start()
+ config_dic['host_threads'][ host['uuid'] ] = thread
+
+
+
+ #Create thread to listen to web requests
+ http_thread = httpserver.httpserver(db_http, 'http', config_dic['http_host'], config_dic['http_port'], False, config_dic)
+ http_thread.start()
+
+ if 'http_admin_port' in config_dic:
+ db_http = create_database_connection(config_dic)
+ http_thread_admin = httpserver.httpserver(db_http, 'http-admin', config_dic['http_host'], config_dic['http_admin_port'], True)
+ http_thread_admin.start()
+ else:
+ http_thread_admin = None
+ time.sleep(1)
+ logger.info('Waiting for http clients')
+ print ('openvimd ready')
+ print ('====================')
+ sys.stdout.flush()
+
+ #TODO: Interactive console would be nice here instead of join or sleep
+
+ r="help" #force print help at the beginning
+ while True:
+ if r=='exit':
+ break
+ elif r!='':
+ print "type 'exit' for terminate"
+ r = raw_input('> ')
+
+ except (KeyboardInterrupt, SystemExit):
+ pass
+
+ logger.info('Exiting openvimd')
+ threads = config_dic.get('host_threads', {})
+ if 'of_thread' in config_dic:
+ threads['of'] = (config_dic['of_thread'])
+ if 'dhcp_thread' in config_dic:
+ threads['dhcp'] = (config_dic['dhcp_thread'])
+
+ for thread in threads.values():
+ thread.insert_task("exit")
+ for thread in threads.values():
+ thread.join()
+ #http_thread.join()
+ #if http_thread_admin is not None:
+ #http_thread_admin.join()
+ logger.debug( "bye!")
+ exit()
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno
+# July 2015
+
+# Personalize RHEL7.1 on compute nodes
+# Prepared to work with the following network card drivers:
+# tg3, igb drivers for management interfaces
+# ixgbe (Intel Niantic) and i40e (Intel Fortville) drivers for data plane interfaces
+
+# To download:
+# wget https://raw.githubusercontent.com/nfvlabs/openmano/master/scripts/configure-compute-node-RHEL7.1.sh
+# To execute:
+# chmod +x ./configure-compute-node-RHEL7.1.sh
+# sudo ./configure-compute-node-RHEL7.1.sh <user> <iface>
+
+# Assumptions:
+# All virtualization options activated on BIOS (vt-d, vt-x, SR-IOV, no power savings...)
+# RHEL7.1 installed without /home partition and with the following packages selection:
+# @base, @core, @development, @network-file-system-client, @virtualization-hypervisor, @virtualization-platform, @virtualization-tools
+
+
+function usage(){
+ echo -e "Usage: sudo $0 [-y] <user-name> [ <iface-name> [<ip-address>|dhcp] ]"
+ echo -e " Configure compute host for VIM usage. (version 0.4). Params:"
+ echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password"
+ echo -e " <user-name> Create if not exist and configure this user for openvim to connect"
+ echo -e " <iface-name> if suplied creates bridge interfaces on this interface, needed for openvim"
+ echo -e " ip or dhcp if suplied, configure the interface with this ip address (/24) or 'dhcp' "
+}
+
+
+#1 CHECK input parameters
+#1.1 root privileges
+[ "$USER" != "root" ] && echo "Needed root privileges" && usage && exit -1
+
+#1.2 input parameters
+FORCE=""
+while getopts "y" o; do
+ case "${o}" in
+ y)
+ FORCE="yes"
+ ;;
+ *)
+ usage
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+
+if [ $# -lt 1 ]
+then
+ usage
+ exit
+fi
+
+user_name=$1
+interface=$2
+ip_iface=$3
+
+if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null
+then
+ echo "Error: interface '$interface' is not present in the system"
+ usage
+ exit 1
+fi
+
+echo '
+#################################################################
+##### INSTALL NEEDED PACKETS #####
+#################################################################'
+
+# Required packages
+yum repolist
+yum check-update
+yum update -y
+yum install -y screen virt-manager ethtool gcc gcc-c++ xorg-x11-xauth xorg-x11-xinit xorg-x11-deprecated-libs libXtst guestfish hwloc libhugetlbfs-utils libguestfs-tools numactl
+# Selinux management
+yum install -y policycoreutils-python
+
+echo '
+#################################################################
+##### INSTALL USER #####
+#################################################################'
+
+# Add required groups
+groupadd -f nfvgroup
+groupadd -f libvirt #for other operating systems may be libvirtd
+
+# Adds user, default password same as name
+if grep -q "^${user_name}:" /etc/passwd
+then
+ #user exist, add to group
+ echo "adding user ${user_name} to groups libvirt,nfvgroup"
+ usermod -a -G libvirt,nfvgroup -g nfvgroup $user_name
+else
+ #create user if it does not exist
+ [ -z "$FORCE" ] && read -p "user '${user_name}' does not exist, create (Y/n)" kk
+ if ! [ -z "$kk" -o "$kk"="y" -o "$kk"="Y" ]
+ then
+ exit
+ fi
+ echo "creating and configuring user ${user_name}"
+ useradd -m -G libvirt,nfvgroup -g nfvgroup $user_name
+ #Password
+ if [ -z "$FORCE" ]
+ then
+ echo "Provide a password for $user_name"
+ passwd $user_name
+ else
+ echo -e "$user_name\n$user_name" | passwd --stdin $user_name
+ fi
+fi
+
+#Setting default libvirt URI for the user
+echo "Setting default libvirt URI for the user"
+echo "if test -x `which virsh`; then" >> /home/${user_name}/.bash_profile
+echo " export LIBVIRT_DEFAULT_URI=qemu:///system" >> /home/${user_name}/.bash_profile
+echo "fi" >> /home/${user_name}/.bash_profile
+
+echo '
+#################################################################
+##### INSTALL HUGEPAGES ISOLCPUS GRUB #####
+#################################################################'
+
+# Huge pages 1G auto mount
+mkdir -p /mnt/huge
+if ! grep -q "Huge pages" /etc/fstab
+then
+ echo "" >> /etc/fstab
+ echo "# Huge pages" >> /etc/fstab
+ echo "nodev /mnt/huge hugetlbfs pagesize=1GB 0 0" >> /etc/fstab
+ echo "" >> /etc/fstab
+fi
+
+# Huge pages reservation service
+if ! [ -f /usr/lib/systemd/system/hugetlb-gigantic-pages.service ]
+then
+ echo "configuring huge pages service"
+ cat > /usr/lib/systemd/system/hugetlb-gigantic-pages.service << EOL
+[Unit]
+Description=HugeTLB Gigantic Pages Reservation
+DefaultDependencies=no
+Before=dev-hugepages.mount
+ConditionPathExists=/sys/devices/system/node
+ConditionKernelCommandLine=hugepagesz=1G
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/lib/systemd/hugetlb-reserve-pages
+
+[Install]
+WantedBy=sysinit.target
+EOL
+fi
+# Grub virtualization options:
+
+# Get isolcpus
+isolcpus=`gawk 'BEGIN{pre=-2;}
+ ($1=="processor"){pro=$3;}
+ ($1=="core" && $4!=0){
+ if (pre+1==pro){endrange="-" pro}
+ else{cpus=cpus endrange sep pro; sep=","; endrange="";};
+ pre=pro;}
+ END{printf("%s",cpus endrange);}' /proc/cpuinfo`
+
+
+# Huge pages reservation file: reserving all memory apart from 4GB per NUMA node
+# Get the number of hugepages: all memory but 8GB reserved for the OS
+#totalmem=`dmidecode --type 17|grep Size |grep MB |gawk '{suma+=$2} END {print suma/1024}'`
+#hugepages=$(($totalmem-8))
+
+if ! [ -f /usr/lib/systemd/hugetlb-reserve-pages ]
+then
+ cat > /usr/lib/systemd/hugetlb-reserve-pages << EOL
+#!/bin/bash
+nodes_path=/sys/devices/system/node/
+if [ ! -d \$nodes_path ]; then
+ echo "ERROR: \$nodes_path does not exist"
+ exit 1
+fi
+
+reserve_pages()
+{
+ echo \$1 > \$nodes_path/\$2/hugepages/hugepages-1048576kB/nr_hugepages
+}
+
+# This example reserves all available memory apart from 4 GB for linux
+# using 1GB size. You can modify it to your needs or comment the lines
+# to avoid reserve memory in a numa node
+EOL
+ for f in /sys/devices/system/node/node?/meminfo
+ do
+ node=`head -n1 $f | gawk '($5=="kB"){print $2}'`
+ memory=`head -n1 $f | gawk '($5=="kB"){print $4}'`
+ memory=$((memory+1048576-1)) #memory must be ceiled
+ memory=$((memory/1048576)) #from `kB to GB
+ #if memory
+ [ $memory -gt 4 ] && echo "reserve_pages $((memory-4)) node$node" >> /usr/lib/systemd/hugetlb-reserve-pages
+ done
+
+ # Run the following commands to enable huge pages early boot reservation:
+ chmod +x /usr/lib/systemd/hugetlb-reserve-pages
+ systemctl enable hugetlb-gigantic-pages
+fi
+
+# Prepares the text to add at the end of the grub line, including blacklisting ixgbevf driver in the host
+textokernel="intel_iommu=on default_hugepagesz=1G hugepagesz=1G isolcpus=$isolcpus modprobe.blacklist=ixgbevf modprobe.blacklist=i40evf"
+
+# Add text to the kernel line
+if ! grep -q "intel_iommu=on default_hugepagesz=1G hugepagesz=1G" /etc/default/grub
+then
+ echo "adding cmdline ${textokernel}"
+ sed -i "/^GRUB_CMDLINE_LINUX=/s/\"\$/ ${textokernel}\"/" /etc/default/grub
+ # grub2 upgrade
+ grub2-mkconfig -o /boot/grub2/grub.cfg
+fi
+
+echo '
+#################################################################
+##### OTHER CONFIGURATION #####
+#################################################################'
+
+# Disable requiretty
+if ! grep -q "#openmano" /etc/sudoers
+then
+ cat >> /home/${user_name}/script_visudo.sh << EOL
+#!/bin/bash
+cat \$1 | awk '(\$0~"requiretty"){print "#"\$0}(\$0!~"requiretty"){print \$0}' > tmp
+cat tmp > \$1
+rm tmp
+EOL
+ chmod +x /home/${user_name}/script_visudo.sh
+ echo "Disabling requitetty"
+ export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo
+ rm -f /home/${user_name}/script_visudo.sh
+fi
+
+#Configure polkint to run virsh as a normal user
+echo "Configuring polkint to run virsh as a normal user"
+cat >> /etc/polkit-1/localauthority/50-local.d/50-org.libvirt-access.pkla << EOL
+[libvirt Admin Access]
+Identity=unix-group:libvirt
+Action=org.libvirt.unix.manage
+ResultAny=yes
+ResultInactive=yes
+ResultActive=yes
+EOL
+
+# Links the OpenMANO required folder /opt/VNF/images to /var/lib/libvirt/images. The OS installation
+# should have only a / partition with all possible space available
+
+echo " link /opt/VNF/images to /var/lib/libvirt/images"
+if [ "$user_name" != "" ]
+then
+ #mkdir -p /home/${user_name}/VNF_images
+ #chown -R ${user_name}:nfvgroup /home/${user_name}/VNF_images
+ #chmod go+x $HOME
+
+ # The orchestator needs to link the images folder
+ rm -f /opt/VNF/images
+ mkdir -p /opt/VNF/
+ ln -s /var/lib/libvirt/images /opt/VNF/images
+ chown -R ${user_name}:nfvgroup /opt/VNF
+ chown -R root:nfvgroup /var/lib/libvirt/images
+ chmod g+rwx /var/lib/libvirt/images
+
+ # Selinux management
+ #echo "configure Selinux management"
+ #semanage fcontext -a -t virt_image_t "/home/${user_name}/VNF_images(/.*)?"
+ #cat /etc/selinux/targeted/contexts/files/file_contexts.local |grep virt_image
+ #restorecon -R -v /home/${user_name}/VNF_images
+else
+ mkdir -p /opt/VNF/images
+ chmod o+rx /opt/VNF/images
+fi
+
+echo "creating local information /opt/VNF/images/hostinfo.yaml"
+echo "#By default openvim assumes control plane interface naming as em1,em2,em3,em4 " > /opt/VNF/images/hostinfo.yaml
+echo "#and bridge ifaces as virbrMan1, virbrMan2, ..." >> /opt/VNF/images/hostinfo.yaml
+echo "#if compute node contain a different name it must be indicated in this file" >> /opt/VNF/images/hostinfo.yaml
+echo "#with the format extandard-name: compute-name" >> /opt/VNF/images/hostinfo.yaml
+if [ "$interface" != "" -a "$interface" != "em1" ]
+then
+ echo "iface_names:" >> /opt/VNF/images/hostinfo.yaml
+ echo " em1: ${interface}" >> /opt/VNF/images/hostinfo.yaml
+fi
+chmod o+r /opt/VNF/images/hostinfo.yaml
+
+# deactivate memory overcommit
+echo "deactivate memory overcommit"
+service ksmtuned stop
+service ksm stop
+chkconfig ksmtuned off
+chkconfig ksm off
+
+
+# Libvirt options (uncomment the following)
+echo "configure Libvirt options"
+sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_dir = "\/var\/run\/libvirt"/unix_sock_dir = "\/var\/run\/libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#auth_unix_rw = "none"/auth_unix_rw = "none"/' /etc/libvirt/libvirtd.conf
+
+#creating the polkit grant access for libvirt user.
+#This does not work !!!! so commented. No way to get running without uncomented the auth_unix_rw = "none" line
+#
+#cat > /etc/polkit-1/localauthority/50-local.d/50-org.example-libvirt-remote-access.pkla << EOL
+#[libvirt Management Access]
+# Identity=unix-user:n2;unix-user:kk
+# Action=org.libvirt.unix.manage
+# ResultAny=yes
+# ResultInactive=yes
+# ResultActive=yes
+#EOL
+
+# Configuration change of qemu for the numatune bug issue
+# RHEL7.1: for this version should not be necesary - to revise
+#if ! grep -q "cgroup_controllers = [ \"cpu\", \"devices\", \"memory\", \"blkio\", \"cpuacct\" ]" /etc/libvirt/qemu.conf
+#then
+#cat /etc/libvirt/qemu.conf | awk '{print $0}($0~"#cgroup_controllers"){print "cgroup_controllers = [ \"cpu\", \"devices\", \"memory\", \"blkio\", \"cpuacct\" ]"}' > tmp
+#mv tmp /etc/libvirt/qemu.conf
+#fi
+
+echo '
+#################################################################
+##### NETWORK CONFIGURATION #####
+#################################################################'
+# Network config (if the second parameter is net)
+if [ -n "$interface" ]
+then
+
+ # Deactivate network manager
+ systemctl stop NetworkManager
+ systemctl disable NetworkManager
+
+ # For management and data interfaces
+ rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs
+
+ pushd /etc/sysconfig/network-scripts/
+
+ # Set ONBOOT=on and MTU=9000 on the interface used for the bridges
+ echo "configuring iface $interface"
+ cat ifcfg-$interface | grep -e HWADDR -e UUID > $interface.tmp
+ echo "TYPE=Ethernet
+NAME=$interface
+DEVICE=$interface
+TYPE=Ethernet
+ONBOOT=yes
+NM_CONTROLLED=no
+MTU=9000
+BOOTPROTO=none
+IPV6INIT=no" >> $interface.tmp
+ mv $interface.tmp ifcfg-$interface
+
+ # Management interfaces
+# integrated_interfaces=""
+# nb_ifaces=0
+# for iface in `ifconfig -a | grep ":\ " | cut -f 1 -d":"| grep -v "_" | grep -v "\." | grep -v "lo" | sort`
+# do
+# driver=`ethtool -i $iface| awk '($0~"driver"){print $2}'`
+# if [ $driver != "ixgbe" ] && [ $driver != "bridge" ]
+# then
+# integrated_interfaces="$integrated_interfaces $iface"
+# nb_ifaces=$((nb_ifaces+1))
+# eval iface${nb_ifaces}=$iface
+# fi
+# done
+
+ #Create infrastructure bridge, normally used for connecting to compute nodes, openflow controller, ...
+ echo "DEVICE=virbrInf
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+USERCTL=no" > ifcfg-virbrInf
+
+ #Create VLAN for infrastructure bridge
+ echo "DEVICE=${interface}.1001
+ONBOOT=yes
+NM_CONTROLLED=no
+USERCTL=no
+VLAN=yes
+BOOTPROTO=none
+BRIDGE=virbrInf" > ifcfg-${interface}.1001
+
+
+ #Create bridge interfaces
+ echo "Creating bridge ifaces: "
+ for ((i=1;i<=20;i++))
+ do
+ i2digits=$i
+ [ $i -lt 10 ] && i2digits="0$i"
+ echo " virbrMan$i vlan 20$i2digits"
+ echo "DEVICE=virbrMan$i
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+USERCTL=no" > ifcfg-virbrMan$i
+
+#Without IP:
+#BOOTPROTO=static
+#IPADDR=10.10.10.$((i+209))
+#NETMASK=255.255.255.0" > ifcfg-virbrMan$i
+
+ # create the required interfaces to connect the bridges
+ echo "DEVICE=${interface}.20$i2digits
+ONBOOT=yes
+NM_CONTROLLED=no
+USERCTL=no
+VLAN=yes
+BOOTPROTO=none
+BRIDGE=virbrMan$i" > ifcfg-${interface}.20$i2digits
+ done
+
+ if [ -n "$ip_iface" ]
+ then
+ echo "configuring iface $iface interface with ip $ip_iface"
+ # Network interfaces
+ # 1Gbps interfaces are configured with ONBOOT=yes and static IP address
+ cat ifcfg-$iface | grep -e HWADDR -e UUID > $iface.tmp
+ echo "TYPE=Ethernet
+NAME=$iface
+DEVICE=$iface
+TYPE=Ethernet
+ONBOOT=yes
+NM_CONTROLLED=no
+IPV6INIT=no" >> $iface.tmp
+ [ $ip_iface = "dhcp" ] && echo -e "BOOTPROTO=dhcp\nDHCP_HOSTNAME=$HOSTNAME" >> $iface.tmp
+ [ $ip_iface != "dhcp" ] && echo -e "BOOTPROTO=static\nIPADDR=${ip_iface}\nNETMASK=255.255.255.0" >> $iface.tmp
+ mv $iface.tmp ifcfg-$iface
+ fi
+
+ for iface in `ifconfig -a | grep ": " | cut -f 1 -d":" | grep -v -e "_" -e "\." -e "lo" -e "virbr" -e "tap"`
+ do
+ # 10/40 Gbps interfaces
+ # Intel X520 cards: driver ixgbe
+ # Intel XL710 Fortville cards: driver i40e
+ driver=`ethtool -i $iface| awk '($0~"driver"){print $2}'`
+ if [ "$driver" = "i40e" -o "$driver" = "ixgbe" ]
+ then
+ echo "configuring dataplane iface $iface"
+
+ # Create 8 SR-IOV per PF by udev rules only for Fortville cards (i40e driver)
+ if [ "$driver" = "i40e" ]
+ then
+ pci=`ethtool -i $iface | awk '($0~"bus-info"){print $2}'`
+ echo "ACTION==\"add\", KERNEL==\"$pci\", SUBSYSTEM==\"pci\", RUN+=\"/usr/bin/bash -c 'echo 8 > /sys/bus/pci/devices/$pci/sriov_numvfs'\"" >> /etc/udev/rules.d/pci_config.rules
+ fi
+
+ # Configure PF to boot automatically and to have a big MTU
+ # 10Gbps interfaces are configured with ONBOOT=yes and MTU=2000
+ cat ifcfg-$iface | grep -e HWADDR -e UUID > $iface.tmp
+ echo "TYPE=Ethernet
+NAME=$iface
+DEVICE=$iface
+ONBOOT=yes
+MTU=9000
+NM_CONTROLLED=no
+IPV6INIT=no
+BOOTPROTO=none" >> $iface.tmp
+ mv $iface.tmp ifcfg-$iface
+ fi
+ done
+ popd
+fi
+
+
+# Activate 8 Virtual Functions per PF on Niantic cards (ixgbe driver)
+if [[ `lsmod | cut -d" " -f1 | grep "ixgbe" | grep -v vf` ]]
+then
+ if ! grep -q "ixgbe" /etc/modprobe.d/ixgbe.conf
+ then
+ echo "options ixgbe max_vfs=8" >> /etc/modprobe.d/ixgbe.conf
+ fi
+
+fi
+
+# Executes dracut to load drivers on boot
+echo "Regenerating initramfs"
+dracut --force
+
+# To define 8 VFs per PF we do it on rc.local, because the driver needs to be unloaded and loaded again
+#if ! grep -q "NFV" /etc/rc.local
+#then
+# echo "" >> /etc/rc.local
+# echo "# NFV" >> /etc/rc.local
+# echo "modprobe -r ixgbe" >> /etc/rc.local
+# echo "modprobe ixgbe max_vfs=8" >> /etc/rc.local
+# echo "" >> /etc/rc.local
+
+# chmod +x /etc/rc.d/rc.local
+
+#fi
+
+echo
+echo "Do not forget to create a shared (NFS, Samba, ...) where original virtual machine images are allocated"
+echo
+echo "Do not forget to copy the public ssh key into /home/${user_name}/.ssh/authorized_keys for authomatic login from openvim controller"
+echo
+
+echo "Reboot the system to make the changes effective"
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno
+# 2016 March 18
+# Modified to run on grub2 and efi boot
+
+# Personalize RHEL7.2 on compute nodes
+# Prepared to work with the following network card drivers:
+# tg3, igb drivers for management interfaces
+# ixgbe (Intel Niantic) and i40e (Intel Fortville) drivers for data plane interfaces
+
+# To download:
+# wget https://raw.githubusercontent.com/nfvlabs/openmano/master/scripts/configure-compute-node-RHEL7.1.sh
+# To execute:
+# chmod +x ./configure-compute-node-RHEL7.1.sh
+# sudo ./configure-compute-node-RHEL7.1.sh <user> <iface>
+
+# Assumptions:
+# All virtualization options activated on BIOS (vt-d, vt-x, SR-IOV, no power savings...)
+# RHEL7.2 installed without /home partition and with the following packages selection:
+# @base, @core, @development, @network-file-system-client, @virtualization-hypervisor, @virtualization-platform, @virtualization-tools
+
+# 2016 Aug 17 Antonio López
+# Changed virbrInf to virbrVIM, to reflect that this bridge is used to communicate with the VIM (OpenVIM)
+# Changed the vlan tag used by virbrVIM from 2000 to 1100
+
+function usage(){
+ echo -e "Usage: sudo $0 [-y] <user-name> [ <iface-name> [<ip-address>|dhcp] ]"
+ echo -e " Configure compute host for VIM usage. (version 0.4). Params:"
+ echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password"
+ echo -e " <user-name> Create if not exist and configure this user for openvim to connect"
+ echo -e " <iface-name> if suplied creates bridge interfaces on this interface, needed for openvim"
+ echo -e " ip or dhcp if suplied, configure the interface with this ip address (/24) or 'dhcp' "
+}
+
+
+#1 CHECK input parameters
+#1.1 root privileges
+[ "$USER" != "root" ] && echo "Needed root privileges" && usage && exit -1
+
+#1.2 input parameters
+FORCE=""
+while getopts "y" o; do
+ case "${o}" in
+ y)
+ FORCE="yes"
+ ;;
+ *)
+ usage
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+
+if [ $# -lt 1 ]
+then
+ usage
+ exit
+fi
+
+user_name=$1
+interface=$2
+ip_iface=$3
+
+if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null
+then
+ echo "Error: interface '$interface' is not present in the system"
+ usage
+ exit 1
+fi
+
+echo '
+#################################################################
+##### INSTALL NEEDED PACKETS #####
+#################################################################'
+
+# Required packages
+yum repolist
+yum check-update
+yum update -y
+yum install -y screen virt-manager ethtool gcc gcc-c++ xorg-x11-xauth xorg-x11-xinit xorg-x11-deprecated-libs libXtst guestfish hwloc libhugetlbfs-utils libguestfs-tools numactl
+# Selinux management
+yum install -y policycoreutils-python
+
+echo '
+#################################################################
+##### INSTALL USER #####
+#################################################################'
+
+# Add required groups
+groupadd -f nfvgroup
+groupadd -f libvirt #for other operating systems may be libvirtd
+
+# Adds user, default password same as name
+if grep -q "^${user_name}:" /etc/passwd
+then
+ #user exist, add to group
+ echo "adding user ${user_name} to groups libvirt,nfvgroup"
+ usermod -a -G libvirt,nfvgroup -g nfvgroup $user_name
+else
+ #create user if it does not exist
+ [ -z "$FORCE" ] && read -p "user '${user_name}' does not exist, create (Y/n)" kk
+ if ! [ -z "$kk" -o "$kk"="y" -o "$kk"="Y" ]
+ then
+ exit
+ fi
+ echo "creating and configuring user ${user_name}"
+ useradd -m -G libvirt,nfvgroup -g nfvgroup $user_name
+ #Password
+ if [ -z "$FORCE" ]
+ then
+ echo "Provide a password for $user_name"
+ passwd $user_name
+ else
+ echo -e "$user_name\n$user_name" | passwd --stdin $user_name
+ fi
+fi
+
+#Setting default libvirt URI for the user
+echo "Setting default libvirt URI for the user"
+echo "if test -x `which virsh`; then" >> /home/${user_name}/.bash_profile
+echo " export LIBVIRT_DEFAULT_URI=qemu:///system" >> /home/${user_name}/.bash_profile
+echo "fi" >> /home/${user_name}/.bash_profile
+
+echo '
+#################################################################
+##### INSTALL HUGEPAGES ISOLCPUS GRUB #####
+#################################################################'
+
+# Huge pages 1G auto mount
+mkdir -p /mnt/huge
+if ! grep -q "Huge pages" /etc/fstab
+then
+ echo "" >> /etc/fstab
+ echo "# Huge pages" >> /etc/fstab
+ echo "nodev /mnt/huge hugetlbfs pagesize=1GB 0 0" >> /etc/fstab
+ echo "" >> /etc/fstab
+fi
+
+# Huge pages reservation service
+if ! [ -f /usr/lib/systemd/system/hugetlb-gigantic-pages.service ]
+then
+ echo "configuring huge pages service"
+ cat > /usr/lib/systemd/system/hugetlb-gigantic-pages.service << EOL
+[Unit]
+Description=HugeTLB Gigantic Pages Reservation
+DefaultDependencies=no
+Before=dev-hugepages.mount
+ConditionPathExists=/sys/devices/system/node
+ConditionKernelCommandLine=hugepagesz=1G
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/lib/systemd/hugetlb-reserve-pages
+
+[Install]
+WantedBy=sysinit.target
+EOL
+fi
+# Grub virtualization options:
+
+# Get isolcpus
+isolcpus=`gawk 'BEGIN{pre=-2;}
+ ($1=="processor"){pro=$3;}
+ ($1=="core" && $4!=0){
+ if (pre+1==pro){endrange="-" pro}
+ else{cpus=cpus endrange sep pro; sep=","; endrange="";};
+ pre=pro;}
+ END{printf("%s",cpus endrange);}' /proc/cpuinfo`
+
+
+# Huge pages reservation file: reserving all memory apart from 4GB per NUMA node
+# Get the number of hugepages: all memory but 8GB reserved for the OS
+#totalmem=`dmidecode --type 17|grep Size |grep MB |gawk '{suma+=$2} END {print suma/1024}'`
+#hugepages=$(($totalmem-8))
+
+if ! [ -f /usr/lib/systemd/hugetlb-reserve-pages ]
+then
+ cat > /usr/lib/systemd/hugetlb-reserve-pages << EOL
+#!/bin/bash
+nodes_path=/sys/devices/system/node/
+if [ ! -d \$nodes_path ]; then
+ echo "ERROR: \$nodes_path does not exist"
+ exit 1
+fi
+
+reserve_pages()
+{
+ echo \$1 > \$nodes_path/\$2/hugepages/hugepages-1048576kB/nr_hugepages
+}
+
+# This example reserves all available memory apart from 4 GB for linux
+# using 1GB size. You can modify it to your needs or comment the lines
+# to avoid reserve memory in a numa node
+EOL
+ for f in /sys/devices/system/node/node?/meminfo
+ do
+ node=`head -n1 $f | gawk '($5=="kB"){print $2}'`
+ memory=`head -n1 $f | gawk '($5=="kB"){print $4}'`
+ memory=$((memory+1048576-1)) #memory must be ceiled
+ memory=$((memory/1048576)) #from `kB to GB
+ #if memory
+ [ $memory -gt 4 ] && echo "reserve_pages $((memory-4)) node$node" >> /usr/lib/systemd/hugetlb-reserve-pages
+ done
+
+ # Run the following commands to enable huge pages early boot reservation:
+ chmod +x /usr/lib/systemd/hugetlb-reserve-pages
+ systemctl enable hugetlb-gigantic-pages
+fi
+
+# Prepares the text to add at the end of the grub line, including blacklisting ixgbevf driver in the host
+
+textokernel="intel_iommu=on default_hugepagesz=1G hugepagesz=1G isolcpus=$isolcpus modprobe.blacklist=ixgbevf modprobe.blacklist=i40evf"
+
+# Add text to the kernel line
+if ! grep -q "intel_iommu=on default_hugepagesz=1G hugepagesz=1G" /etc/default/grub
+then
+ echo "adding cmdline ${textokernel}"
+ sed -i "/^GRUB_CMDLINE_LINUX=/s/\"\$/ ${textokernel}\"/" /etc/default/grub
+
+ # grub2 upgrade
+
+ # BIOS based systems
+ grub2-mkconfig -o /boot/grub2/grub.cfg
+
+ # UEFI based systems
+ grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
+
+fi
+
+echo '
+#################################################################
+##### OTHER CONFIGURATION #####
+#################################################################'
+
+# Disable requiretty
+if ! grep -q "#openmano" /etc/sudoers
+then
+ cat >> /home/${user_name}/script_visudo.sh << EOL
+#!/bin/bash
+cat \$1 | awk '(\$0~"requiretty"){print "#"\$0}(\$0!~"requiretty"){print \$0}' > tmp
+cat tmp > \$1
+rm tmp
+EOL
+ chmod +x /home/${user_name}/script_visudo.sh
+ echo "Disabling requitetty"
+ export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo
+ rm -f /home/${user_name}/script_visudo.sh
+fi
+
+#Configure polkint to run virsh as a normal user
+echo "Configuring polkint to run virsh as a normal user"
+cat >> /etc/polkit-1/localauthority/50-local.d/50-org.libvirt-access.pkla << EOL
+[libvirt Admin Access]
+Identity=unix-group:libvirt
+Action=org.libvirt.unix.manage
+ResultAny=yes
+ResultInactive=yes
+ResultActive=yes
+EOL
+
+# Links the OpenMANO required folder /opt/VNF/images to /var/lib/libvirt/images. The OS installation
+# should have only a / partition with all possible space available
+
+echo " link /opt/VNF/images to /var/lib/libvirt/images"
+if [ "$user_name" != "" ]
+then
+ #mkdir -p /home/${user_name}/VNF_images
+ #chown -R ${user_name}:nfvgroup /home/${user_name}/VNF_images
+ #chmod go+x $HOME
+
+ # The orchestator needs to link the images folder
+ rm -f /opt/VNF/images
+ mkdir -p /opt/VNF/
+ ln -s /var/lib/libvirt/images /opt/VNF/images
+ chown -R ${user_name}:nfvgroup /opt/VNF
+ chown -R root:nfvgroup /var/lib/libvirt/images
+ chmod g+rwx /var/lib/libvirt/images
+
+ # Selinux management
+ #echo "configure Selinux management"
+ #semanage fcontext -a -t virt_image_t "/home/${user_name}/VNF_images(/.*)?"
+ #cat /etc/selinux/targeted/contexts/files/file_contexts.local |grep virt_image
+ #restorecon -R -v /home/${user_name}/VNF_images
+else
+ mkdir -p /opt/VNF/images
+ chmod o+rx /opt/VNF/images
+fi
+
+echo "creating local information /opt/VNF/images/hostinfo.yaml"
+echo "#By default openvim assumes control plane interface naming as em1,em2,em3,em4 " > /opt/VNF/images/hostinfo.yaml
+echo "#and bridge ifaces as virbrMan1, virbrMan2, ..." >> /opt/VNF/images/hostinfo.yaml
+echo "#if compute node contain a different name it must be indicated in this file" >> /opt/VNF/images/hostinfo.yaml
+echo "#with the format extandard-name: compute-name" >> /opt/VNF/images/hostinfo.yaml
+if [ "$interface" != "" -a "$interface" != "em1" ]
+then
+ echo "iface_names:" >> /opt/VNF/images/hostinfo.yaml
+ echo " em1: ${interface}" >> /opt/VNF/images/hostinfo.yaml
+fi
+chmod o+r /opt/VNF/images/hostinfo.yaml
+
+# deactivate memory overcommit
+echo "deactivate memory overcommit"
+service ksmtuned stop
+service ksm stop
+chkconfig ksmtuned off
+chkconfig ksm off
+
+
+# Libvirt options (uncomment the following)
+echo "configure Libvirt options"
+sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_dir = "\/var\/run\/libvirt"/unix_sock_dir = "\/var\/run\/libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#auth_unix_rw = "none"/auth_unix_rw = "none"/' /etc/libvirt/libvirtd.conf
+
+#creating the polkit grant access for libvirt user.
+#This does not work !!!! so commented. No way to get running without uncomented the auth_unix_rw = "none" line
+#
+#cat > /etc/polkit-1/localauthority/50-local.d/50-org.example-libvirt-remote-access.pkla << EOL
+#[libvirt Management Access]
+# Identity=unix-user:n2;unix-user:kk
+# Action=org.libvirt.unix.manage
+# ResultAny=yes
+# ResultInactive=yes
+# ResultActive=yes
+#EOL
+
+# Configuration change of qemu for the numatune bug issue
+# RHEL7.1: for this version should not be necesary - to revise
+#if ! grep -q "cgroup_controllers = [ \"cpu\", \"devices\", \"memory\", \"blkio\", \"cpuacct\" ]" /etc/libvirt/qemu.conf
+#then
+#cat /etc/libvirt/qemu.conf | awk '{print $0}($0~"#cgroup_controllers"){print "cgroup_controllers = [ \"cpu\", \"devices\", \"memory\", \"blkio\", \"cpuacct\" ]"}' > tmp
+#mv tmp /etc/libvirt/qemu.conf
+#fi
+
+echo '
+#################################################################
+##### NETWORK CONFIGURATION #####
+#################################################################'
+# Network config (if the second parameter is net)
+if [ -n "$interface" ]
+then
+
+ # Deactivate network manager
+ systemctl stop NetworkManager
+ systemctl disable NetworkManager
+
+ # For management and data interfaces
+ #rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs
+
+ pushd /etc/sysconfig/network-scripts/
+
+ # Set ONBOOT=on and MTU=9000 on the interface used for the bridges
+ echo "configuring iface $interface"
+ cat ifcfg-$interface | grep -e HWADDR -e UUID > $interface.tmp
+ echo "TYPE=Ethernet
+NAME=$interface
+DEVICE=$interface
+TYPE=Ethernet
+ONBOOT=yes
+NM_CONTROLLED=no
+MTU=9000
+BOOTPROTO=none
+IPV6INIT=no" >> $interface.tmp
+ mv $interface.tmp ifcfg-$interface
+
+ # Management interfaces
+# integrated_interfaces=""
+# nb_ifaces=0
+# for iface in `ifconfig -a | grep ":\ " | cut -f 1 -d":"| grep -v "_" | grep -v "\." | grep -v "lo" | sort`
+# do
+# driver=`ethtool -i $iface| awk '($0~"driver"){print $2}'`
+# if [ $driver != "ixgbe" ] && [ $driver != "bridge" ]
+# then
+# integrated_interfaces="$integrated_interfaces $iface"
+# nb_ifaces=$((nb_ifaces+1))
+# eval iface${nb_ifaces}=$iface
+# fi
+# done
+
+ #Create infrastructure bridge, normally used for connecting to compute nodes, openflow controller, ...
+ echo "DEVICE=virbrVIM
+NAME=virbrVIM
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+MTU=9000
+USERCTL=no" > ifcfg-virbrVIM
+[[ $ip_iface != "dhcp" ]] && [[ $ip_iface != "" ]] && echo -e "BOOTPROTO=static\nIPADDR=${ip_iface}\nNETMASK=255.255.255.0" >> ifcfg-virbrVIM
+
+ #Create VLAN for infrastructure bridge
+ echo "DEVICE=${interface}.1100
+NAME=${interface}.1100
+ONBOOT=yes
+NM_CONTROLLED=no
+USERCTL=no
+VLAN=yes
+MTU=9000
+BOOTPROTO=none
+BRIDGE=virbrVIM" > ifcfg-${interface}.1100
+
+
+ #Create bridge interfaces
+ echo "Creating bridge ifaces: "
+ for ((i=1;i<=20;i++))
+ do
+ i2digits=$i
+ [ $i -lt 10 ] && i2digits="0$i"
+ echo " virbrMan$i vlan 20$i2digits"
+ echo "DEVICE=virbrMan$i
+NAME=virbrMan$i
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+MTU=9000
+USERCTL=no" > ifcfg-virbrMan$i
+
+#Without IP:
+#BOOTPROTO=static
+#IPADDR=10.10.10.$((i+209))
+#NETMASK=255.255.255.0" > ifcfg-virbrMan$i
+
+ # create the required interfaces to connect the bridges
+ echo "DEVICE=${interface}.20$i2digits
+NAME=${interface}.20$i2digits
+ONBOOT=yes
+NM_CONTROLLED=no
+USERCTL=no
+VLAN=yes
+BOOTPROTO=none
+MTU=9000
+BRIDGE=virbrMan$i" > ifcfg-${interface}.20$i2digits
+ done
+
+ iface=$interface
+ if [ -n "$ip_iface" ]
+ then
+ echo "configuring iface $iface interface with ip $ip_iface"
+ # Network interfaces
+ # 1Gbps interfaces are configured with ONBOOT=yes and static IP address
+ cat ifcfg-$iface | grep -e HWADDR -e UUID > $iface.tmp
+ echo "TYPE=Ethernet
+NAME=$iface
+DEVICE=$iface
+TYPE=Ethernet
+ONBOOT=yes
+NM_CONTROLLED=no
+MTU=9000
+IPV6INIT=no" >> $iface.tmp
+ [ $ip_iface = "dhcp" ] && echo -e "BOOTPROTO=dhcp\nDHCP_HOSTNAME=$HOSTNAME" >> $iface.tmp
+ [ $ip_iface != "dhcp" ] && echo -e "BOOTPROTO=static\nIPADDR=${ip_iface}\nNETMASK=255.255.255.0" >> $iface.tmp
+ mv $iface.tmp ifcfg-$iface
+ fi
+ # Script to create vfs
+ echo "#!/bin/bash" > /root/activate-vfs.sh
+ chmod +x /root/activate-vfs.sh
+ for iface in `ifconfig -a | grep ": " | cut -f 1 -d":" | grep -v -e "_" -e "\." -e "lo" -e "virbr" -e "tap"`
+ do
+ # 10/40 Gbps interfaces
+ # Intel X520 cards: driver ixgbe
+ # Intel XL710 Fortville cards: driver i40e
+ driver=`ethtool -i $iface| awk '($0~"driver"){print $2}'`
+ if [ "$driver" = "i40e" -o "$driver" = "ixgbe" ]
+ then
+ echo "configuring dataplane iface $iface"
+
+ # Create 8 SR-IOV per PF by udev rules only for Fortville cards (i40e driver)
+ if [ "$driver" = "i40e" ]
+ then
+ pci=`ethtool -i $iface | awk '($0~"bus-info"){print $2}'`
+ echo "echo 8 > /sys/bus/pci/devices/$pci/sriov_numvfs" >> /root/activate-vfs.sh
+ fi
+
+ # Configure PF to boot automatically and to have a big MTU
+ # 10Gbps interfaces are configured with ONBOOT=yes and MTU=2000
+ cat ifcfg-$iface | grep -e HWADDR -e UUID > $iface.tmp
+ echo "TYPE=Ethernet
+NAME=$iface
+DEVICE=$iface
+ONBOOT=yes
+MTU=9000
+NM_CONTROLLED=no
+IPV6INIT=no
+BOOTPROTO=none" >> $iface.tmp
+ mv $iface.tmp ifcfg-$iface
+ fi
+ done
+ popd
+fi
+# add entry in rc.local for activate-vfs
+grep -q 'touch /var/lock/subsys/local' '/etc/rc.d/rc.local'
+if [[ $? == 0 ]]
+then
+ echo "/root/activate-vfs.sh" >> /etc/rc.local
+fi
+
+
+
+# Activate 8 Virtual Functions per PF on Niantic cards (ixgbe driver)
+if [[ `lsmod | cut -d" " -f1 | grep "ixgbe" | grep -v vf` ]]
+then
+ if ! grep -q "ixgbe" /etc/modprobe.d/ixgbe.conf
+ then
+ echo "options ixgbe max_vfs=8" >> /etc/modprobe.d/ixgbe.conf
+ fi
+
+fi
+
+# Executes dracut to load drivers on boot
+echo "Regenerating initramfs"
+dracut --force
+
+# To define 8 VFs per PF we do it on rc.local, because the driver needs to be unloaded and loaded again
+#if ! grep -q "NFV" /etc/rc.local
+#then
+# echo "" >> /etc/rc.local
+# echo "# NFV" >> /etc/rc.local
+# echo "modprobe -r ixgbe" >> /etc/rc.local
+# echo "modprobe ixgbe max_vfs=8" >> /etc/rc.local
+# echo "" >> /etc/rc.local
+
+# chmod +x /etc/rc.d/rc.local
+
+#fi
+
+echo
+echo "Do not forget to create a shared (NFS, Samba, ...) where original virtual machine images are allocated"
+echo
+echo "Do not forget to copy the public ssh key of openvim user into /home/${user_name}/.ssh/authorized_keys for authomatic login from openvim controller"
+echo
+
+echo "Reboot the system to make the changes effective"
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno
+# June 2015
+
+# Personalize RHEL7.1 on compute nodes
+# Prepared to work with the following network card drivers:
+# tg3, igb drivers for management interfaces
+# ixgbe (Intel Niantic) and i40e (Intel Fortville) drivers for data plane interfaces
+
+# To download:
+# wget https://raw.githubusercontent.com/nfvlabs/openmano/master/scripts/configure-compute-node-RHEL7.1.sh
+# To execute:
+# chmod +x ./configure-compute-node-RHEL7.1.sh
+# sudo ./configure-compute-node-RHEL7.1.sh <user> <iface>
+
+# Assumptions:
+# All virtualization options activated on BIOS (vt-d, vt-x, SR-IOV, no power savings...)
+# RHEL7.1 installed without /home partition and with the following packages selection:
+# @base, @core, @development, @network-file-system-client, @virtualization-hypervisor, @virtualization-platform, @virtualization-tools
+
+
+function usage(){
+ echo -e "Usage: sudo $0 [-y] <user-name> [ <iface-name> [<ip-address>|dhcp] ]"
+ echo -e " Configure compute host for VIM usage. (version 0.4). Params:"
+ echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password"
+ echo -e " <user-name> Create if not exist and configure this user for openvim to connect"
+ echo -e " <iface-name> if suplied creates bridge interfaces on this interface, needed for openvim"
+ echo -e " ip or dhcp if suplied, configure the interface with this ip address (/24) or 'dhcp' "
+}
+
+
+#1 CHECK input parameters
+#1.1 root privileges
+[ "$USER" != "root" ] && echo "Needed root privileges" && usage && exit -1
+
+#1.2 input parameters
+FORCE=""
+while getopts "y" o; do
+ case "${o}" in
+ y)
+ FORCE="yes"
+ ;;
+ *)
+ usage
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+
+
+if [ $# -lt 1 ]
+then
+ usage
+ exit
+fi
+
+
+user_name=$1
+interface=$2
+ip_iface=$3
+
+if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null
+then
+ echo "Error: interface '$interface' is not present in the system"
+ usage
+ exit 1
+fi
+
+echo '
+#################################################################
+##### INSTALL NEEDED PACKETS #####
+#################################################################'
+
+# Required packages
+apt-get -y update
+#apt-get -y install grub-common screen virt-manager ethtool build-essential x11-common x11-utils x11-apps libguestfs-tools hwloc libguestfs-tools numactl vlan nfs-common nfs-kernel-server
+apt-get -y install grub-common screen virt-manager ethtool build-essential x11-common x11-utils libguestfs-tools hwloc libguestfs-tools numactl vlan nfs-common nfs-kernel-server
+
+echo "Remove unneeded packages....."
+apt-get -y autoremove
+# Selinux management
+#yum install -y policycoreutils-python
+
+
+
+echo '
+#################################################################
+##### INSTALL USER #####
+#################################################################'
+
+# Add required groups
+groupadd -f admin
+groupadd -f libvirt #for other operating systems may be libvirtd
+
+# Adds user, default password same as name
+if grep -q "^${user_name}:" /etc/passwd
+then
+ #user exist, add to group
+ echo "adding user ${user_name} to groups libvirt,admin"
+ usermod -a -G libvirt,admin -g admin $user_name
+else
+ #create user if it does not exist
+ [ -z "$FORCE" ] && read -p "user '${user_name}' does not exist, create (Y/n)" kk
+ if ! [ -z "$kk" -o "$kk"="y" -o "$kk"="Y" ]
+ then
+ exit
+ fi
+ echo "creating and configuring user ${user_name}"
+ useradd -m -G libvirt,admin -g admin $user_name
+ #Password
+ if [ -z "$FORCE" ]
+ then
+ echo "Provide a password for $user_name"
+ passwd $user_name
+ else
+ echo -e "$user_name\n$user_name" | passwd --stdin $user_name
+ fi
+fi
+
+# Allow admin users to access without password
+if ! grep -q "#openmano" /etc/sudoers
+then
+ cat >> /home/${user_name}/script_visudo.sh << EOL
+#!/bin/bash
+cat \$1 | awk '(\$0~"requiretty"){print "#"\$0}(\$0!~"requiretty"){print \$0}' > tmp
+cat tmp > \$1
+rm tmp
+echo "" >> \$1
+echo "#openmano allow to group admin to grant root privileges without password" >> \$1
+echo "%admin ALL=(ALL) NOPASSWD: ALL" >> \$1
+EOL
+ chmod +x /home/${user_name}/script_visudo.sh
+ echo "allowing admin user to get root privileges withut password"
+ export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo
+ rm -f /home/${user_name}/script_visudo.sh
+fi
+
+
+echo '
+#################################################################
+##### INSTALL HUGEPAGES ISOLCPUS GRUB #####
+#################################################################'
+
+# Huge pages 1G auto mount
+mkdir -p /mnt/huge
+if ! grep -q "Huge pages" /etc/fstab
+then
+ echo "" >> /etc/fstab
+ echo "# Huge pages" >> /etc/fstab
+ echo "nodev /mnt/huge hugetlbfs pagesize=1GB 0 0" >> /etc/fstab
+ echo "" >> /etc/fstab
+fi
+
+# Grub virtualization options:
+
+# Get isolcpus
+isolcpus=`gawk 'BEGIN{pre=-2;}
+ ($1=="processor"){pro=$3;}
+ ($1=="core" && $4!=0){
+ if (pre+1==pro){endrange="-" pro}
+ else{cpus=cpus endrange sep pro; sep=","; endrange="";};
+ pre=pro;}
+ END{printf("%s",cpus endrange);}' /proc/cpuinfo`
+
+
+echo "CPUS: $isolcpus"
+
+# Huge pages reservation file: reserving all memory apart from 4GB per NUMA node
+# Get the number of hugepages: all memory but 8GB reserved for the OS
+#totalmem=`dmidecode --type 17|grep Size |grep MB |gawk '{suma+=$2} END {print suma/1024}'`
+#hugepages=$(($totalmem-8))
+
+if ! [ -f /usr/lib/systemd/hugetlb-reserve-pages ]
+then
+ cat > /usr/lib/systemd/hugetlb-reserve-pages << EOL
+#!/bin/bash
+nodes_path=/sys/devices/system/node/
+if [ ! -d \$nodes_path ]; then
+ echo "ERROR: \$nodes_path does not exist"
+ exit 1
+fi
+
+reserve_pages()
+{
+ echo \$1 > \$nodes_path/\$2/hugepages/hugepages-1048576kB/nr_hugepages
+}
+
+# This example reserves all available memory apart from 4 GB for linux
+# using 1GB size. You can modify it to your needs or comment the lines
+# to avoid reserve memory in a numa node
+EOL
+ for f in /sys/devices/system/node/node?/meminfo
+ do
+ node=`head -n1 $f | gawk '($5=="kB"){print $2}'`
+ memory=`head -n1 $f | gawk '($5=="kB"){print $4}'`
+ memory=$((memory+1048576-1)) #memory must be ceiled
+ memory=$((memory/1048576)) #from `kB to GB
+ #if memory
+ [ $memory -gt 4 ] && echo "reserve_pages $((memory-4)) node$node" >> /usr/lib/systemd/hugetlb-reserve-pages
+ done
+
+ # Run the following commands to enable huge pages early boot reservation:
+ chmod +x /usr/lib/systemd/hugetlb-reserve-pages
+ systemctl enable hugetlb-gigantic-pages
+fi
+
+# Prepares the text to add at the end of the grub line, including blacklisting ixgbevf driver in the host
+memtotal=`grep MemTotal /proc/meminfo | awk '{ print $2 }' `
+hpages=$(( ($memtotal/(1024*1024))-8 ))
+
+memtotal=$((memtotal+1048576-1)) #memory must be ceiled
+memtotal=$((memtotal/1048576)) #from `kB to GBa
+hpages=$((memtotal-8))
+[[ $hpages -lt 0 ]] $$ hpages=0
+
+
+echo "------> memtotal: $memtotal"
+
+textokernel="intel_iommu=on default_hugepagesz=1G hugepagesz=1G hugepages=$hpages isolcpus=$isolcpus modprobe.blacklist=ixgbevf modprobe.blacklist=i40evf"
+
+echo "Text to kernel: $textokernel"
+
+
+# Add text to the kernel line
+if ! grep -q "intel_iommu=on default_hugepagesz=1G hugepagesz=1G" /etc/default/grub
+then
+ echo ">>>>>>> adding cmdline ${textokernel}"
+ sed -i "/^GRUB_CMDLINE_LINUX_DEFAULT=/s/\"\$/${textokernel}\"/" /etc/default/grub
+ # grub2 upgrade
+ #grub2-mkconfig -o /boot/grub2/grub.cfg
+ update-grub
+fi
+
+echo '
+#################################################################
+##### OTHER CONFIGURATION #####
+#################################################################'
+
+# Links the OpenMANO required folder /opt/VNF/images to /var/lib/libvirt/images. The OS installation
+# should have only a / partition with all possible space available
+
+echo " link /opt/VNF/images to /var/lib/libvirt/images"
+if [ "$user_name" != "" ]
+then
+ #mkdir -p /home/${user_name}/VNF_images
+ #chown -R ${user_name}:admin /home/${user_name}/VNF_images
+ #chmod go+x $HOME
+
+ # The orchestator needs to link the images folder
+ rm -f /opt/VNF/images
+ mkdir -p /opt/VNF/
+ ln -s /var/lib/libvirt/images /opt/VNF/images
+ chown -R ${user_name}:admin /opt/VNF
+ chown -R root:admin /var/lib/libvirt/images
+ chmod g+rwx /var/lib/libvirt/images
+
+ # Selinux management
+ #echo "configure Selinux management"
+ #semanage fcontext -a -t virt_image_t "/home/${user_name}/VNF_images(/.*)?"
+ #cat /etc/selinux/targeted/contexts/files/file_contexts.local |grep virt_image
+ #restorecon -R -v /home/${user_name}/VNF_images
+else
+ mkdir -p /opt/VNF/images
+ chmod o+rx /opt/VNF/images
+fi
+
+echo "creating local information /opt/VNF/images/hostinfo.yaml"
+echo "#By default openvim assumes control plane interface naming as em1,em2,em3,em4 " > /opt/VNF/images/hostinfo.yaml
+echo "#and bridge ifaces as virbrMan1, virbrMan2, ..." >> /opt/VNF/images/hostinfo.yaml
+echo "#if compute node contain a different name it must be indicated in this file" >> /opt/VNF/images/hostinfo.yaml
+echo "#with the format extandard-name: compute-name" >> /opt/VNF/images/hostinfo.yaml
+if [ "$interface" != "" -a "$interface" != "em1" ]
+then
+ echo "iface_names:" >> /opt/VNF/images/hostinfo.yaml
+ echo " em1: ${interface}" >> /opt/VNF/images/hostinfo.yaml
+fi
+chmod o+r /opt/VNF/images/hostinfo.yaml
+
+# deactivate memory overcommit
+#echo "deactivate memory overcommit"
+#service ksmtuned stop
+#service ksm stop
+#chkconfig ksmtuned off
+#chkconfig ksm off
+
+
+# Libvirt options (uncomment the following)
+echo "configure Libvirt options"
+sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_dir = "\/var\/run\/libvirt"/unix_sock_dir = "\/var\/run\/libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#auth_unix_rw = "none"/auth_unix_rw = "none"/' /etc/libvirt/libvirtd.conf
+
+
+echo '
+#################################################################
+##### NETWORK CONFIGURATION #####
+#################################################################'
+# Network config (if the second parameter is net)
+echo "Interface ==> $interface"
+if [ -n "$interface" ]
+then
+
+
+ # For management and data interfaces
+ rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs
+
+
+ # Set ONBOOT=on and MTU=9000 on the interface used for the bridges
+ echo "configuring iface $interface"
+
+#MTU for interfaces and bridges
+MTU=9000
+
+cp /etc/network/interfaces interfaces.tmp
+
+
+ #Create infrastructure bridge, normally used for connecting to compute nodes, openflow controller, ...
+
+
+ #Create VLAN for infrastructure bridge
+
+ echo "
+######### CUTLINE #########
+
+auto ${interface}
+iface ${interface} inet static
+ mtu $MTU
+
+auto ${interface}.1001
+iface ${interface}.1001 inet static
+ mtu $MTU
+" >> interfaces.tmp
+
+ echo "ifconfig ${interface} mtu $MTU
+ ifconfig ${interface} up
+" > mtu.tmp
+
+
+ #Create bridge interfaces
+ echo "Creating bridge ifaces: "
+ for ((i=1;i<=20;i++))
+ do
+ i2digits=$i
+ [ $i -lt 10 ] && i2digits="0$i"
+ echo " virbrMan$i vlan 20$i2digits"
+
+ j=$i
+
+ echo "
+auto ${interface}.20$i2digits
+iface ${interface}.20$i2digits inet static
+ mtu $MTU
+
+auto virbrMan$j
+iface virbrMan$j inet static
+ bridge_ports ${interface}.20$i2digits
+ mtu $MTU
+" >> interfaces.tmp
+
+ echo "ifconfig ${interface}.20$i2digits mtu $MTU
+ifconfig virbrMan$j mtu $MTU
+ifconfig virbrMan$j up
+" >> mtu.tmp
+
+ done
+
+ echo "
+auto em2.1001
+iface em2.1001 inet static
+
+auto virbrInf
+iface virbrInf inet static
+ bridge_ports em2.1001
+" >> interfaces.tmp
+
+ echo "ifconfig em2.1001 mtu $MTU
+ifconfig virbrInf mtu $MTU
+ifconfig virbrInf up
+" >> mtu.tmp
+
+if ! grep -q "#### CUTLINE ####" /etc/network/interfaces
+then
+ echo "====== Copying interfaces.tmp to /etc/network/interfaces"
+ cp interfaces.tmp /etc/network/interfaces
+fi
+
+
+ #popd
+fi
+
+
+# Activate 8 Virtual Functions per PF on Niantic cards (ixgbe driver)
+if [[ `lsmod | cut -d" " -f1 | grep "ixgbe" | grep -v vf` ]]
+then
+ if ! grep -q "ixgbe" /etc/modprobe.d/ixgbe.conf
+ then
+ echo "options ixgbe max_vfs=8" >> /etc/modprobe.d/ixgbe.conf
+ fi
+
+fi
+
+# Set dataplane MTU
+
+echo "sleep 10" >> mtu.tmp
+
+interfaces=`ifconfig -a | grep ^p | cut -d " " -f 1`
+for ph in $interfaces
+do
+ echo "ifconfig $ph mtu $MTU" >> mtu.tmp
+ echo "ifconfig $ph up" >> mtu.tmp
+done
+
+
+
+cp mtu.tmp /etc/setmtu.sh
+chmod +x /etc/setmtu.sh
+
+# To define 8 VFs per PF we do it on rc.local, because the driver needs to be unloaded and loaded again
+#if ! grep -q "NFV" /etc/rc.local
+#then
+ echo "#!/bin/sh -e
+" > /etc/rc.local
+ echo "# NFV" >> /etc/rc.local
+ echo "modprobe -r ixgbe" >> /etc/rc.local
+ echo "modprobe ixgbe max_vfs=8" >> /etc/rc.local
+ echo "/etc/setmtu.sh" >> /etc/rc.local
+ echo "
+exit 0" >> /etc/rc.local
+ echo "" >> /etc/rc.local
+
+ chmod +x /etc/rc.d/rc.local
+
+#fi
+
+chmod a+rwx /var/lib/libvirt/images
+mkdir /usr/libexec/
+pushd /usr/libexec/
+ln -s /usr/bin/qemu-system-x86_64 qemu-kvm
+popd
+
+#Deactivating apparmor while looking for a better solution
+/etc/init.d/apparmor stop
+update-rc.d -f apparmor remove
+
+echo
+echo "Do not forget to create a shared (NFS, Samba, ...) where original virtual machine images are allocated"
+echo
+echo "Do not forget to copy the public ssh key into /home/${user_name}/.ssh/authorized_keys for authomatic login from openvim controller"
+echo
+
+echo "Reboot the system to make the changes effective"
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+# v1.0: 2015 June
+# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno
+
+# Personalize RHEL7/CENTOS compute nodes for using openvim in 'development' mode:
+# not using huge pages neither isolcpus
+
+# To download:
+# wget https://raw.githubusercontent.com/nfvlabs/openmano/master/scripts/configure-compute-node-develop.sh
+# To execute:
+# chmod +x ./configure-compute-node-develop.sh
+# sudo ./configure-compute-node-develop.sh <user> <iface>
+
+function usage(){
+ echo -e "Usage: sudo $0 [-y] <user-name> [ <iface-name> [<ip-address>|dhcp] ]"
+ echo -e " Configure compute host for VIM usage in mode 'development'. Params:"
+ echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password"
+ echo -e " <user-name> Create if not exist and configure this user for openvim to connect"
+ echo -e " <iface-name> if supplied creates bridge interfaces on this interface, needed for openvim"
+ echo -e " ip or dhcp if supplied, configure the interface with this ip address (/24) or 'dhcp' "
+}
+
+#1 CHECK input parameters
+#1.1 root privileges
+[ "$USER" != "root" ] && echo "Needed root privileges" && usage && exit -1
+
+#1.2 input parameters
+FORCE=""
+while getopts "y" o; do
+ case "${o}" in
+ y)
+ FORCE="yes"
+ ;;
+ *)
+ usage
+ exit -1
+ ;;
+ esac
+done
+shift $((OPTIND-1))
+
+
+if [ $# -lt 1 ]
+then
+ usage
+ exit
+fi
+
+user_name=$1
+interface=$2
+ip_iface=$3
+
+if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null
+then
+ echo "Error: interface '$interface' is not present in the system"
+ usage
+ exit 1
+fi
+
+echo '
+#################################################################
+##### INSTALL NEEDED PACKETS #####
+#################################################################'
+
+# Required packages
+yum repolist
+yum check-update
+yum update -y
+yum install -y screen virt-manager ethtool gcc gcc-c++ xorg-x11-xauth xorg-x11-xinit xorg-x11-deprecated-libs libXtst guestfish hwloc libhugetlbfs-utils libguestfs-tools
+# Selinux management
+yum install -y policycoreutils-python
+
+echo '
+#################################################################
+##### INSTALL USER #####
+#################################################################'
+
+# Add required groups
+groupadd -f admin
+groupadd -f libvirt #for other operating systems may be libvirtd
+
+# Adds user, default password same as name
+if grep -q "^${user_name}:" /etc/passwd
+then
+ #user exist, add to group
+ echo "adding user ${user_name} to groups libvirt,admin"
+ usermod -a -G libvirt,admin -g admin $user_name
+else
+ #create user if it does not exist
+ [ -z "$FORCE" ] && read -p "user '${user_name}' does not exist, create (Y/n)" kk
+ if ! [ -z "$kk" -o "$kk"="y" -o "$kk"="Y" ]
+ then
+ exit
+ fi
+ echo "creating and configuring user ${user_name}"
+ useradd -m -G libvirt,admin -g admin $user_name
+ #Password
+ if [ -z "$FORCE" ]
+ then
+ echo "Provide a password for $user_name"
+ passwd $user_name
+ else
+ echo -e "$user_name\n$user_name" | passwd --stdin $user_name
+ fi
+fi
+
+# Allow admin users to access without password
+if ! grep -q "#openmano" /etc/sudoers
+then
+ cat >> /home/${user_name}/script_visudo.sh << EOL
+#!/bin/bash
+cat \$1 | awk '(\$0~"requiretty"){print "#"\$0}(\$0!~"requiretty"){print \$0}' > tmp
+cat tmp > \$1
+rm tmp
+echo "" >> \$1
+echo "#openmano allow to group admin to grant root privileges without password" >> \$1
+echo "%admin ALL=(ALL) NOPASSWD: ALL" >> \$1
+EOL
+ chmod +x /home/${user_name}/script_visudo.sh
+ echo "allowing admin user to get root privileges withut password"
+ export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo
+ rm -f /home/${user_name}/script_visudo.sh
+fi
+
+echo '
+#################################################################
+##### OTHER CONFIGURATION #####
+#################################################################'
+# Creates a folder to store images in the user home
+#Creates a link to the /home folder because in RHEL this folder is larger
+echo "creating compute node folder for local images /opt/VNF/images"
+if [ "$user_name" != "" ]
+then
+ mkdir -p /home/VNF_images
+ chown -R ${user_name}:admin /home/VNF_images
+ chmod go+x /home/VNF_images
+
+ # The orchestator needs to link the images folder
+ rm -f /opt/VNF/images
+ mkdir -p /opt/VNF/
+ ln -s /home/VNF_images /opt/VNF/images
+ chown -R ${user_name}:admin /opt/VNF
+
+else
+ mkdir -p /opt/VNF/images
+ chmod o+rx /opt/VNF/images
+fi
+
+echo "creating local information /opt/VNF/images/hostinfo.yaml"
+echo "#By default openvim assumes control plane interface naming as em1,em2,em3,em4 " > /opt/VNF/images/hostinfo.yaml
+echo "#and bridge ifaces as virbrMan1, virbrMan2, ..." >> /opt/VNF/images/hostinfo.yaml
+echo "#if compute node contain a different name it must be indicated in this file" >> /opt/VNF/images/hostinfo.yaml
+echo "#with the format extandard-name: compute-name" >> /opt/VNF/images/hostinfo.yaml
+if [ "$interface" != "" -a "$interface" != "em1" ]
+then
+ echo "iface_names:" >> /opt/VNF/images/hostinfo.yaml
+ echo " em1: ${interface}" >> /opt/VNF/images/hostinfo.yaml
+fi
+chmod o+r /opt/VNF/images/hostinfo.yaml
+
+# deactivate memory overcommit
+echo "deactivate memory overcommit"
+service ksmtuned stop
+service ksm stop
+chkconfig ksmtuned off
+chkconfig ksm off
+
+# Libvirt options (uncomment the following)
+echo "configure Libvirt options"
+sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#unix_sock_dir = "\/var\/run\/libvirt"/unix_sock_dir = "\/var\/run\/libvirt"/' /etc/libvirt/libvirtd.conf
+sed -i 's/#auth_unix_rw = "none"/auth_unix_rw = "none"/' /etc/libvirt/libvirtd.conf
+
+echo '
+#################################################################
+##### NETWORK CONFIGURATION #####
+#################################################################'
+# Network config (if the second parameter is net)
+if [ -n "$interface" ]
+then
+
+ # Deactivate network manager
+ #systemctl stop NetworkManager
+ #systemctl disable NetworkManager
+
+ pushd /etc/sysconfig/network-scripts/
+
+ #Create infrastructure bridge
+ echo "DEVICE=virbrInf
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+IPADDR=10.10.0.1
+NETMASK=255.255.255.0
+USERCTL=no" > ifcfg-virbrInf
+
+ #Create bridge interfaces
+ echo "Creating bridge ifaces: "
+ for ((i=1;i<=20;i++))
+ do
+ i2digits=$i
+ [ $i -lt 10 ] && i2digits="0$i"
+ echo " virbrMan$i"
+ echo "DEVICE=virbrMan$i
+TYPE=Bridge
+ONBOOT=yes
+DELAY=0
+NM_CONTROLLED=no
+USERCTL=no" > ifcfg-virbrMan$i
+
+ done
+
+ popd
+fi
+
+echo
+echo "Do not forget to create a folder where original virtual machine images are allocated (ex. $HOME/static_storage)"
+echo
+echo "Do not forget to allow openvim machine accessing directly to the host with ssh. Can be done by:"
+echo " Copy the public ssh key of the openvim user from $HOME/.ssh/id_dsa.pub (in openvim) into /home/${user_name}/.ssh/authorized_keys (in the host) for automatic login from openvim controller"
+echo " Or running on openvim machine 'ssh-keygen' (generate ssh keys) and 'ssh-copy-id <user>@<compute host>'"
+echo
+echo "Do not forget to perform an initial ssh login from openmano VM into the host so the openmano ssh host key is added to /home/${user_name}/.ssh/known_hosts"
+echo
+
+echo "Reboot the system to make the changes effective"
+
+
--- /dev/null
+<!--
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+-->
+
+<configuration scan="true">
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%level [%logger:%thread] %msg%n</pattern>
+ </encoder>
+ </appender>
+ <root level="ERROR">
+ <appender-ref ref="STDOUT" />
+ </root>
+ <logger name="org" level="ERROR"/>
+ <logger name="LogService" level="ERROR"/> <!-- Restlet access logging -->
+ <logger name="net.floodlightcontroller" level="TRACE"/>
+ <logger name="net.floodlightcontroller.logging" level="ERROR"/>
+</configuration>
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+floodlight.modules = net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.FloodlightProvider,\
+net.floodlightcontroller.threadpool.ThreadPool,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.firewall.Firewall,\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.counter.CounterStore,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.ui.web.StaticWebRoutable
+
+#PORT API floodlight will listen to. Must match the 'of_controller_port' of openvimd.cfg
+net.floodlightcontroller.restserver.RestApiServer.port = 7070
+
+#PORT used by the switch to connect to floodlight
+net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
+net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
+
+#timeout parameters
+net.floodlightcontroller.forwarding.Forwarding.idletimeout = 5
+net.floodlightcontroller.forwarding.Forwarding.hardtimeout = 0
+
--- /dev/null
+floodlight.modules=\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.internal.FloodlightProvider,\
+net.floodlightcontroller.threadpool.ThreadPool,\
+net.floodlightcontroller.debugcounter.DebugCounterServiceImpl,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.debugevent.DebugEventService,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.restserver.RestApiServer,\
+net.floodlightcontroller.topology.TopologyManager,\
+net.floodlightcontroller.forwarding.Forwarding,\
+net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager,\
+net.floodlightcontroller.ui.web.StaticWebRoutable,\
+net.floodlightcontroller.loadbalancer.LoadBalancer,\
+net.floodlightcontroller.firewall.Firewall,\
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl
+org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE
+org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/auth_credentials.jceks
+org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/
+org.sdnplatform.sync.internal.SyncManager.port=6642
+net.floodlightcontroller.core.internal.FloodlightProvider.openflowPort=6653
+net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE
+net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnInitialHandshakeAsMaster=YES
+net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnEachTransitionToMaster=YES
+net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePath=/path/to/your/keystore-file.jks
+net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePassword=your-keystore-password
+net.floodlightcontroller.core.internal.OFSwitchManager.useSsl=NO
+net.floodlightcontroller.restserver.RestApiServer.keyStorePath=/path/to/your/keystore-file.jks
+net.floodlightcontroller.restserver.RestApiServer.keyStorePassword=your-keystore-password
+net.floodlightcontroller.restserver.RestApiServer.httpsNeedClientAuthentication=NO
+net.floodlightcontroller.restserver.RestApiServer.useHttps=NO
+net.floodlightcontroller.restserver.RestApiServer.useHttp=YES
+net.floodlightcontroller.restserver.RestApiServer.httpsPort=8081
+net.floodlightcontroller.restserver.RestApiServer.httpPort=8080
+
--- /dev/null
+#!/bin/bash
+awk '
+($1=="lease" && $3=="{"){ lease=$2; active="no"; found="no" }
+($1=="binding" && $2=="state" && $3=="active;"){ active="yes" }
+($1=="hardware" && $2=="ethernet" && $3==tolower("'$1';")){ found="yes" }
+($1=="client-hostname"){ name=$2 }
+($1=="}"){ if (active=="yes" && found=="yes"){ target_lease=lease; target_name=name}}
+END{printf("%s", target_lease)} #print target_name
+' /var/lib/dhcp/dhcpd.leases
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#Get configuration of a host for using it as a compute node
+
+function usage(){
+ echo -e "usage: $0 user ip_name nb_cores GiB_memory nb_10GB_interfaces [hostname] [>> host.yaml]\n Get host parameters and generated a yaml file to be used for openvim host-add"
+ echo -e " - In case hostname is not specified it will be used the name of the machine where the script is run"
+ echo -e " - nb_cores must be an odd number and bigger or equal to 4."
+ echo -e " - GiB_memory must be an odd number and bigger or equal to 16. 4GiB of memory will be reserved for the host OS, the rest will be used by VM."
+ echo -e " - nb_10GB_interfaces must be an odd number and bigger or equal to 4."
+ echo -e " - The output will be a server descriptor with two numas and resources (memory, cores and interfaces) equally distributed between them."
+ echo -e " - Each interface (physical funtion) will have defined 8 SR-IOV (virtual functions)."
+ exit 1
+}
+
+function get_hash_value() { echo `eval echo $\{\`echo $1[$2]\`\}`; }
+
+function get_mac(){
+ seed=$1
+ b1=$((seed%16)); seed=$((seed/16))
+ b2=$((seed%16)); seed=$((seed/16))
+ b3=$((seed%16)); seed=$((seed/16))
+ b4=$((seed%16)); seed=$((seed/16))
+ b5=$((seed%16)); seed=$((seed/16))
+ mac=`printf "%02X:%02X:%02X:%02X:%02X:%02X" 2 $b5 $b4 $b3 $b2 $b1`
+ echo $mac
+}
+
+
+#check root privileges and non a root user behind
+
+[ "$#" -lt "5" ] && echo "Missing parameters" && usage
+[ "$#" -gt "6" ] && echo "Too many parameters" && usage
+HOST_NAME=`cat /etc/hostname`
+[ "$#" -eq "6" ] && HOST_NAME=$6
+FEATURES_LIST="lps,dioc,hwsv,tlbps,ht,lps,64b,iommu"
+NUMAS=2
+CORES=$3
+MEMORY=$4
+INTERFACES=$5
+
+#Ensure the user input is big enough
+([ $((CORES%2)) -ne 0 ] || [ $CORES -lt 4 ] ) && echo -e "ERROR: Wrong number of cores\n" && usage
+([ $((MEMORY%2)) -ne 0 ] || [ $MEMORY -lt 16 ] ) && echo -e "ERROR: Wrong number of memory\n" && usage
+([ $((INTERFACES%2)) -ne 0 ] || [ $INTERFACES -lt 4 ] ) && echo -e "ERROR: Wrong number of interfaces\n" && usage
+
+#Generate a cpu topology for 4 numas with hyperthreading
+CPUS=`pairs_gap=$((CORES/NUMAS));numa=0;inc=0;sibling=0;for((thread=0;thread<=$((pairs_gap-1));thread++)); do printf " ${numa}-${sibling}-${thread} ${numa}-${sibling}-$((thread+pairs_gap))";numa=$(((numa+1)%$NUMAS)); sibling=$((sibling+inc)); inc=$(((inc+1)%2)); done`
+
+#in this developing/fake server all cores can be used
+
+echo "#This file was created by $0"
+echo "#for adding this compute node to openvim"
+echo "#copy this file to openvim controller and run"
+echo "#openvim host-add <this>"
+echo
+echo "host:"
+echo " name: $HOST_NAME"
+echo " user: $1"
+echo " ip_name: $2"
+echo "host-data:"
+echo " name: $HOST_NAME"
+echo " user: $1"
+echo " ip_name: $2"
+echo " ranking: 100"
+echo " description: $HOST_NAME"
+echo " features: $FEATURES_LIST"
+echo " numas:"
+
+numa=0
+last_iface=0
+iface_counter=0
+while [ $numa -lt $NUMAS ]
+do
+ echo " - numa_socket: $numa"
+#MEMORY
+ echo " hugepages: $((MEMORY/2-2))"
+ echo " memory: $((MEMORY/2))"
+
+#CORES
+ echo " cores:"
+ for cpu in $CPUS
+ do
+ PHYSICAL=`echo $cpu | cut -f 1 -d"-"`
+ CORE=`echo $cpu | cut -f 2 -d"-"`
+ THREAD=`echo $cpu | cut -f 3 -d"-"`
+ [ $PHYSICAL != $numa ] && continue #skip non physical
+ echo " - core_id: $CORE"
+ echo " thread_id: $THREAD"
+ [ $CORE -eq 0 ] && echo " status: noteligible"
+ done
+
+
+ #GENERATE INTERFACES INFORMATION AND PRINT IT
+ seed=$RANDOM
+ echo " interfaces:"
+ for ((iface=0;iface<$INTERFACES;iface+=2))
+ do
+ name="iface$iface_counter"
+ bus=$((iface+last_iface))
+ pci=`printf "0000:%02X:00.0" $bus`
+ mac=`get_mac $seed`
+ seed=$((seed+1))
+
+ echo " - source_name: $name"
+ echo " Mbps: 10000"
+ echo " pci: \"$pci\""
+ echo " mac: \"$mac\""
+ echo " switch_dpid: \"01:02:03:04:05:06\""
+ echo " switch_port: fake0/$iface_counter"
+ echo " sriovs:"
+
+ for((nb_sriov=0;nb_sriov<8;nb_sriov++))
+ do
+ pci=`printf "0000:%02X:10.%i" $bus $nb_sriov`
+ mac=`get_mac $seed`
+ seed=$((seed+1))
+ echo " - mac: \"$mac\""
+ echo " pci: \"$pci\""
+ echo " source_name: $nb_sriov"
+ done
+
+ iface_counter=$((iface_counter+1))
+ done
+ last_iface=$(((numa+1)*127/NUMAS+5)) #made-up formula for more realistic pci numbers
+
+
+ numa=$((numa+1))
+done
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#Get configuration of a host for using it as a compute node
+
+function usage(){
+ echo -e "usage: $0 user ip_name [>> host.yaml]\n Get host parameters and generated a yaml file to be used for openvim host-add"
+ exit 1
+}
+
+function load_vf_driver(){
+ local pf_driver=$1
+ if [[ `lsmod | cut -d" " -f1 | grep $pf_driver | grep -v vf` ]] && [[ ! `lsmod | cut -d" " -f1 | grep ${pf_driver}vf` ]]
+ then
+ >&2 echo "$pf_driver is loaded but not ${pf_driver}vf. This is required in order to properly add SR-IOV."
+ read -p "Do you want to load ${pf_driver}vf [Y/n] " load_driver
+ case $load_driver in
+ [nN]* ) exit 1;;
+ * ) >&2 echo "Loading ${pf_driver}vf..."
+ modprobe ${pf_driver}vf;
+ >&2 echo "Reloading ${pf_driver}..."
+ modprobe -r $pf_driver;
+ modprobe $pf_driver;;
+ esac
+ fi
+}
+
+function remove_vf_driver(){
+ local pf_driver=$1
+ if [[ `lsmod | cut -d" " -f1 | grep $pf_driver | grep -v vf` ]] && [[ `lsmod | cut -d" " -f1 | grep ${pf_driver}vf` ]]
+ then
+ >&2 echo "${pf_driver}vf is loaded. In order to ensure proper SR-IOV behavior the driver must be removed."
+ read -p "Do you want to remove ${pf_driver}vf now? [Y/n] " remove_driver
+ case $remove_driver in
+ [nN]* ) >&2 echo "OK. Remember to remove the driver prior start using the compute node executing:";
+ >&2 echo "modprobe -r ${pf_driver}vf";
+ >&2 echo "modprobe -r ${pf_driver}";
+ >&2 echo "modprobe ${pf_driver}";;
+ * ) >&2 echo "Removing ${pf_driver}vf..."
+ modprobe -r ${pf_driver}vf;
+ >&2 echo "Reloading ${pf_driver}..."
+ modprobe -r $pf_driver;
+ modprobe $pf_driver;;
+ esac
+ fi
+}
+
+function get_hash_value() { echo `eval echo $\{\`echo $1[$2]\`\}`; }
+
+function xmlpath_args()
+{
+ local expr="${1//\// }"
+ local path=()
+ local chunk tag data
+ local exit_code=1
+ local print_line=0
+ local closing_tag=0
+
+ while IFS='' read -r -d '<' chunk; do
+ data=arguments=""
+ IFS='>' read -r tag_arg data <<< "$chunk"
+ IFS=' ' read -r tag arguments <<< "$tag_arg"
+ #If last tag was single level remove it from path
+ if [[ $closing_tag -eq 1 ]]
+ then
+ unset path[${#path[@]}-1]
+ closing_tag=0
+ fi
+ #In case the tag is closed in the same line mark it
+ [[ $arguments = ?*'/' ]] && closing_tag=1
+ arguments="${arguments//\//}"
+ case "$tag" in
+ '?'*) ;;
+ '!--'*) ;;
+ ?*'/') ;;
+ '/'?*) unset path[${#path[@]}-1] ;;
+ ?*) path+=("$tag");;
+ esac
+
+ #echo "\"${path[@]}\" \"$expr\" \"$data\" \"$arguments\" $exit_code $print_line"
+
+ if [[ "${path[@]}" == "$expr" ]]
+ then
+ #If there is data print it and append arguments if any
+ if [ "$data" != "" ]
+ then
+ echo "$data $arguments"
+ #return code 0 means data was found
+ exit_code=0
+ continue
+ #if there is no data but there are arguments print arguments
+ elif [ "$arguments" != "" ]
+ then
+ echo "$arguments"
+ #return code 2 means no data but arguments were found
+ exit_code=2
+ continue
+ #otherwise switch flag to start/stop echoing each line until the tag is closed
+ elif [[ $exit_code -eq 1 ]]
+ then
+ print_line=$(((print_line+1)%2))
+ #return code 3 means that the whole xml segment is returned
+ exit_code=3
+ fi
+ fi
+ [[ $print_line == "1" ]] && echo "<"$chunk
+ done
+ return $exit_code
+}
+
+
+#check root privileges and non a root user behind
+
+[[ "$#" -lt "2" ]] && echo "Missing parameters" && usage
+load_vf_driver ixgbe
+load_vf_driver i40e
+
+HOST_NAME=`cat /etc/hostname`
+FEATURES=`grep "^flags" /proc/cpuinfo`
+FEATURES_LIST=""
+if echo $FEATURES | grep -q pdpe1gb ; then FEATURES_LIST="${FEATURES_LIST},lps"; fi
+if echo $FEATURES | grep -q dca ; then FEATURES_LIST="${FEATURES_LIST},dioc"; fi
+if echo $FEATURES | egrep -q "(vmx|svm)" ; then FEATURES_LIST="${FEATURES_LIST},hwsv"; fi
+if echo $FEATURES | egrep -q "(ept|npt)" ; then FEATURES_LIST="${FEATURES_LIST},tlbps"; fi
+if echo $FEATURES | grep -q ht ; then FEATURES_LIST="${FEATURES_LIST},ht"; fi
+if uname -m | grep -q x86_64 ; then FEATURES_LIST="${FEATURES_LIST},64b"; fi
+if cat /var/log/dmesg | grep -q -e Intel-IOMMU ; then FEATURES_LIST="${FEATURES_LIST},iommu"; fi
+FEATURES_LIST=${FEATURES_LIST#,}
+
+NUMAS=`gawk 'BEGIN{numas=0;}
+ ($1=="physical" && $2=="id" ){ if ($4+1>numas){numas=$4+1} };
+ END{printf("%d",numas);}' /proc/cpuinfo`
+
+CPUS=`gawk '($1=="processor"){pro=$3;}
+ ($1=="physical" && $2=="id"){ phy=$4;}
+ ($1=="core" && $2=="id"){printf " %d-%d-%d", phy,$4,pro;}' /proc/cpuinfo`
+
+if grep -q isolcpus /proc/cmdline
+then
+ isolcpus=`cat /proc/cmdline`
+ isolcpus=${isolcpus##*isolcpus=}
+ isolcpus=${isolcpus%% *}
+ isolcpus=${isolcpus//,/ }
+else
+ isolcpus=""
+fi
+
+
+#obtain interfaces information
+unset dpid
+read -p "Do you want to provide the interfaces connectivity information (datapathid/dpid of the switch and switch port id)? [Y/n] " conn_info
+case $conn_info in
+ [Nn]* ) prov_conn=false;;
+ * ) prov_conn=true;
+ read -p "What is the switch dapapathid/dpdi? (01:02:03:04:05:06:07:08) " dpid;
+ [[ -z $dpid ]] && dpid="01:02:03:04:05:06:07:08";
+ PORT_RANDOM=$RANDOM
+ iface_counter=0;;
+esac
+OLDIFS=$IFS
+IFS=$'\n'
+unset PF_list
+unset VF_list
+for device in `virsh nodedev-list --cap net | grep -v net_lo_00_00_00_00_00_00`
+do
+virsh nodedev-dumpxml $device > device_xml
+name=`xmlpath_args "device/capability/interface" < device_xml`
+name="${name// /}"
+address=`xmlpath_args "device/capability/address" < device_xml`
+address="${address// /}"
+parent=`xmlpath_args "device/parent" < device_xml`
+parent="${parent// /}"
+#the following line created variables 'speed' and 'state'
+eval `xmlpath_args "device/capability/link" < device_xml`
+virsh nodedev-dumpxml $parent > parent_xml
+driver=`xmlpath_args "device/driver/name" < parent_xml`
+[ $? -eq 1 ] && driver="N/A"
+driver="${driver// /}"
+
+#If the device is not up try to bring it up and reload state
+if [[ $state == 'down' ]] && ( [[ $driver == "ixgbe" ]] || [[ $driver == "i40e" ]] )
+then
+ >&2 echo "$name is down. Trying to bring it up"
+ ifconfig $name up
+ sleep 2
+ virsh nodedev-dumpxml $device > device_xml
+ eval `xmlpath_args "device/capability/link" < device_xml`
+fi
+
+if [[ $state == 'down' ]] && ( [[ $driver == "ixgbe" ]] || [[ $driver == "i40e" ]] )
+then
+ >&2 echo "Interfaces must be connected and up in order to properly detect the speed. You can provide this information manually or skip the interface"
+ keep_asking=true
+ skip_interface=true
+ unset speed
+ while $keep_asking; do
+ read -p "Do you want to skip interface $name ($address) [y/N] " -i "n" skip
+ case $skip in
+ [Yy]* ) keep_asking=false;;
+ * ) skip_interface=false;
+ default_speed="10000"
+ while $keep_asking; do
+ read -p "What is the speed of the interface expressed in Mbps? ($default_speed) " speed;
+ [[ -z $speed ]] && speed=$default_speed
+ [[ $speed =~ ''|*[!0-9] ]] && echo "The input must be an integer" && continue;
+ keep_asking=false ;
+ done;;
+ esac
+ done
+
+ $skip_interface && continue
+fi
+#the following line creates a 'node' variable
+eval `xmlpath_args "device/capability/numa" < parent_xml`
+#the following line creates the variable 'type'
+#in case the interface is a PF the value is 'virt_functions'
+#in case the interface is a VF the value is 'phys_function'
+type="N/A"
+eval `xmlpath_args "device/capability/capability" < parent_xml`
+#obtain pci
+#the following line creates the variables 'domain' 'bus' 'slot' and 'function'
+eval `xmlpath_args "device/capability/iommuGroup/address" < parent_xml`
+pci="${domain#*x}:${bus#*x}:${slot#*x}.${function#*x}"
+underscored_pci="${pci//\:/_}"
+underscored_pci="pci_${underscored_pci//\./_}"
+
+if ( [[ $driver == "ixgbe" ]] || [[ $driver == "i40e" ]] )
+then
+ underscored_pci="pf"$underscored_pci
+ PF_list[${#PF_list[@]}]=$underscored_pci
+ eval declare -A $underscored_pci
+ eval $underscored_pci["name"]=$name
+ eval $underscored_pci["numa"]=$node
+ eval $underscored_pci["mac"]=$address
+ eval $underscored_pci["speed"]=$speed
+ eval $underscored_pci["pci"]=$pci
+ #request switch port to the user if this information is being provided and include it
+ if $prov_conn
+ then
+ unset switch_port
+ read -p "What is the port name in the switch $dpid where port $name ($pci) is connected? (${name}-${PORT_RANDOM}/$iface_counter) " switch_port
+ [[ -z $switch_port ]] && switch_port="${name}-${PORT_RANDOM}/$iface_counter"
+ iface_counter=$((iface_counter+1))
+ eval $underscored_pci["dpid"]=$dpid
+ eval $underscored_pci["switch_port"]=$switch_port
+ fi
+
+ #Añado el pci de cada uno de los hijos
+ SRIOV_counter=0
+ for child in `xmlpath_args "device/capability/capability/address" < parent_xml`
+ do
+ SRIOV_counter=$((SRIOV_counter+1))
+ #the following line creates the variables 'domain' 'bus' 'slot' and 'function'
+ eval $child
+ eval $underscored_pci["SRIOV"$SRIOV_counter]="${domain#*x}_${bus#*x}_${slot#*x}_${function#*x}"
+ done
+ eval $underscored_pci["SRIOV"]=$SRIOV_counter
+
+#Si se trata de un SRIOV (tiene una capability con type 'phys_function')
+elif [[ $type == 'phys_function' ]]
+then
+ underscored_pci="vf"$underscored_pci
+ VF_list[${#VF_list[@]}]=$underscored_pci
+ eval declare -A $underscored_pci
+ eval $underscored_pci["source_name"]=$name
+ eval $underscored_pci["mac"]=$address
+ eval $underscored_pci["pci"]=$pci
+fi
+rm -f device_xml parent_xml
+done
+IFS=$OLDIFS
+
+echo "#This file was created by $0"
+echo "#for adding this compute node to openvim"
+echo "#copy this file to openvim controller and run"
+echo "#openvim host-add <this>"
+echo
+echo "host:"
+echo " name: $HOST_NAME"
+echo " user: $1"
+echo " ip_name: $2"
+echo "host-data:"
+echo " name: $HOST_NAME"
+echo " user: $1"
+echo " ip_name: $2"
+echo " ranking: 100"
+echo " description: $HOST_NAME"
+echo " features: $FEATURES_LIST"
+echo " numas:"
+
+numa=0
+while [[ $numa -lt $NUMAS ]]
+do
+ echo " - numa_socket: $numa"
+#MEMORY
+ if [ -f /sys/devices/system/node/node${numa}/hugepages/hugepages-1048576kB/nr_hugepages ]
+ then
+ echo " hugepages: " `cat /sys/devices/system/node/node${numa}/hugepages/hugepages-1048576kB/nr_hugepages`
+ else
+ #TODO hugepages of 2048kB size
+ echo " hugepages: 0"
+ fi
+ memory=`head -n1 /sys/devices/system/node/node${numa}/meminfo | gawk '($5=="kB"){print $4}'`
+ memory=$((memory+1048576-1)) #memory must be ceiled
+ memory=$((memory/1048576)) #from `kB to GB
+ echo " memory: $memory"
+
+#CORES
+ echo " cores:"
+ FIRST="-" #first item in a list start with "-" in yaml files, then it will set to " "
+ for cpu in $CPUS
+ do
+ PHYSICAL=`echo $cpu | cut -f 1 -d"-"`
+ CORE=`echo $cpu | cut -f 2 -d"-"`
+ THREAD=`echo $cpu | cut -f 3 -d"-"`
+ [[ $PHYSICAL != $numa ]] && continue #skip non physical
+ echo " - core_id: $CORE"
+ echo " thread_id: $THREAD"
+ #check if eligible
+ cpu_isolated="no"
+ for isolcpu in $isolcpus
+ do
+ isolcpu_start=`echo $isolcpu | cut -f 1 -d"-"`
+ isolcpu_end=`echo $isolcpu | cut -f 2 -d"-"`
+ if [ "$THREAD" -ge "$isolcpu_start" -a "$THREAD" -le "$isolcpu_end" ]
+ then
+ cpu_isolated="yes"
+ break
+ fi
+ done
+ [[ $cpu_isolated == "no" ]] && echo " status: noteligible"
+ FIRST=" "
+ done
+
+ #NIC INTERFACES
+ interfaces_nb=0
+ for ((i=0; i<${#PF_list[@]};i++))
+ do
+ underscored_pci=${PF_list[$i]}
+ pname=$(get_hash_value $underscored_pci "name")
+ pnuma=$(get_hash_value $underscored_pci "numa")
+ [[ $pnuma != $numa ]] && continue
+ pmac=$(get_hash_value $underscored_pci "mac")
+ ppci=$(get_hash_value $underscored_pci "pci")
+ pspeed=$(get_hash_value $underscored_pci "speed")
+ pSRIOV=$(get_hash_value $underscored_pci "SRIOV")
+ [[ $interfaces_nb -eq 0 ]] && echo " interfaces:"
+ interfaces_nb=$((interfaces_nb+1))
+ sriov_nb=0
+ echo " - source_name: $pname"
+ echo " Mbps: $pspeed"
+ echo " pci: \"$ppci\""
+ echo " mac: \"$pmac\""
+ if $prov_conn
+ then
+ pdpid=$(get_hash_value $underscored_pci "dpid")
+ pswitch_port=$(get_hash_value $underscored_pci "switch_port")
+ echo " switch_dpid: $pdpid"
+ echo " switch_port: $pswitch_port"
+ fi
+ for ((j=1;j<=$pSRIOV;j++))
+ do
+ childSRIOV="vfpci_"$(get_hash_value $underscored_pci "SRIOV"$j)
+ pname=$(get_hash_value $childSRIOV "source_name")
+ index=${pname##*_}
+ pmac=$(get_hash_value $childSRIOV "mac")
+ ppci=$(get_hash_value $childSRIOV "pci")
+ [[ $sriov_nb -eq 0 ]] && echo " sriovs:"
+ sriov_nb=$((sriov_nb+1))
+ echo " - mac: \"$pmac\""
+ echo " pci: \"$ppci\""
+ echo " source_name: $index"
+ done
+ done
+
+ numa=$((numa+1))
+done
+remove_vf_driver ixgbe
+remove_vf_driver i40e
+#Bring up all interfaces
+for ((i=0; i<${#PF_list[@]};i++))
+do
+ underscored_pci=${PF_list[$i]}
+ pname=$(get_hash_value $underscored_pci "name")
+ ifconfig $pname up
+done
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#This script can be used as a basic test of openvim
+#stopping on an error
+#WARNING: It destroy the database content
+
+
+function usage(){
+ echo -e "usage: ${BASH_SOURCE[0]} [OPTIONS] <action>\n Deletes openvim content and add fake hosts, networks"
+ echo -e " <action> is a list of the following items (by default 'reset create')"
+ echo -e " reset reset the openvim database content"
+ echo -e " create creates fake hosts and networks"
+ echo -e " delete delete created items"
+ echo -e " delete-all delete vms. flavors, images, ..."
+ echo -e " OPTIONS:"
+ echo -e " -f --force : does not prompt for confirmation"
+ echo -e " -d --delete : same to action delete-all"
+ echo -e " --insert-bashrc insert the created tenant variables at"
+ echo -e " ~/.bashrc to be available by openvim CLI"
+ echo -e " -h --help : shows this help"
+}
+
+function is_valid_uuid(){
+ echo "$1" | grep -q -E '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$' && return 0
+ return 1
+}
+
+
+#detect if is called with a source to use the 'exit'/'return' command for exiting
+[[ ${BASH_SOURCE[0]} != $0 ]] && _exit="return" || _exit="exit"
+
+#check correct arguments
+force=""
+action_list=""
+insert_bashrc=""
+
+while [[ $# -gt 0 ]]
+do
+ argument="$1"
+ shift
+ if [[ $argument == reset ]] || [[ $argument == create ]] || [[ $argument == delete ]] || [[ $argument == delete-all ]]
+ then
+ action_list="$action_list $argument"
+ continue
+ #short options
+ elif [[ ${argument:0:1} == "-" ]] && [[ ${argument:1:1} != "-" ]] && [[ ${#argument} -ge 2 ]]
+ then
+ index=0
+ while index=$((index+1)) && [[ $index -lt ${#argument} ]]
+ do
+ [[ ${argument:$index:1} == h ]] && usage && $_exit 0
+ [[ ${argument:$index:1} == f ]] && force=y && continue
+ [[ ${argument:$index:1} == d ]] && action_list="delete-all $action_list" && continue
+ echo "invalid option '${argument:$index:1}'? Type -h for help" >&2 && $_exit 1
+ done
+ continue
+ fi
+ #long options
+ [[ $argument == --help ]] && usage && $_exit 0
+ [[ $argument == --force ]] && force=y && continue
+ [[ $argument == --delete ]] && action_list="delete-all $action_list" && continue
+ [[ $argument == --insert-bashrc ]] && insert_bashrc=y && continue
+ echo "invalid argument '$argument'? Type -h for help" >&2 && $_exit 1
+done
+
+DIRNAME=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
+DIRvim=$(dirname $DIRNAME)
+export OPENVIM_HOST=localhost
+export OPENVIM_PORT=9080
+[[ $insert_bashrc == y ]] && echo -e "\nexport OPENVIM_HOST=localhost" >> ~/.bashrc
+[[ $insert_bashrc == y ]] && echo -e "\nexport OPENVIM_PORT=9080" >> ~/.bashrc
+#by default action should be reset and create
+[[ -z $action_list ]] && action_list="reset create"
+
+
+for action in $action_list
+do
+if [[ $action == "reset" ]]
+then
+ #ask for confirmation if argument is not -f --force
+ force_="y"
+ [[ $force != y ]] && read -e -p "WARNING: openvim database content will be lost!!! Continue(y/N)" force_
+ [[ $force_ != y ]] && [[ $force_ != yes ]] && echo "aborted!" && $_exit
+ echo "deleting deployed vm"
+ ${DIRvim}/openvim vm-delete -f | grep -q deleted && sleep 10 #give some time to get virtual machines deleted
+ echo "Stopping openvim"
+ $DIRNAME/service-openvim.sh stop
+ echo "Initializing databases"
+ $DIRvim/database_utils/init_vim_db.sh -u vim -p vimpw
+ echo "Starting openvim"
+ $DIRNAME/service-openvim.sh start
+
+elif [[ $action == delete-all ]]
+then
+ for t in `${DIRvim}/openvim tenant-list | awk '/^ *[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12} +/{printf("%s:%s",$1,$2)}'`
+ do
+ t_id=${t%%:*}
+ t_name=${t#*:}
+ [[ -z $t_id ]] && continue
+ export OPENVIM_TENANT=${t_id}
+ for what in vm image flavor port net
+ do
+ items=`${DIRvim}/openvim $what-list | awk '/^ *[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12} +/{print $1}'`
+ if [[ -n $items ]]
+ then
+ [[ $force == y ]] && echo deleting openvim ${what}s from tenant ${t_name}
+ [[ $force != y ]] && read -e -p "Delete openvim ${what}s from tenant ${t_name}?(y/N) " force_
+ [[ $force_ != y ]] && [[ $force_ != yes ]] && echo "aborted!" && $_exit
+ for item in $items
+ do
+ echo -n "$item "
+ ${DIRvim}/openvim $what-delete -f $item || ! echo "fail" >&2 || $_exit 1
+ done
+ fi
+ done
+ ${DIRvim}/openvim tenant-delete -f $t_id || ! echo "fail" >&2 || $_exit 1
+ for what in host
+ do
+ items=`${DIRvim}/openvim $what-list | awk '/^ *[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12} +/{print $1}'`
+ if [[ -n $items ]]
+ then
+ [[ $force == y ]] && echo deleting openvim ${what}s
+ [[ $force != y ]] && read -e -p "Delete openvim ${what}s?(y/N) " force_
+ [[ $force_ != y ]] && [[ $force_ != yes ]] && echo "aborted!" && $_exit
+ for item in $items
+ do
+ echo -n "$item "
+ ${DIRvim}/openvim $what-delete -f $item || ! echo "fail" >&2 || $_exit 1
+ done
+ fi
+ done
+
+ done
+elif [[ $action == "delete" ]]
+then
+ ${DIRvim}/openvim net-delete -f default || echo "fail"
+ ${DIRvim}/openvim net-delete -f macvtap:em1 || echo "fail"
+ ${DIRvim}/openvim net-delete -f shared_bridge_net || echo "fail"
+ ${DIRvim}/openvim net-delete -f data_net || echo "fail"
+ ${DIRvim}/openvim host-remove -f fake-host-0 || echo "fail"
+ ${DIRvim}/openvim host-remove -f fake-host-1 || echo "fail"
+ ${DIRvim}/openvim host-remove -f fake-host-2 || echo "fail"
+ ${DIRvim}/openvim host-remove -f fake-host-3 || echo "fail"
+ result=`openvim tenant-list TEST-admin`
+ vimtenant=`echo $result |gawk '{print $1}'`
+ #check a valid uuid is obtained
+ is_valid_uuid $vimtenant || ! echo "Tenant TEST-admin not found. Already delete?" >&2 || $_exit 1
+ export OPENVIM_TENANT=$vimtenant
+ ${DIRvim}/openvim tenant-delete -f TEST-admin || echo "fail"
+ echo
+
+elif [[ $action == "create" ]]
+then
+ echo "Adding example hosts"
+ ${DIRvim}/openvim host-add $DIRvim/test/hosts/host-example0.json || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim host-add $DIRvim/test/hosts/host-example1.json || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim host-add $DIRvim/test/hosts/host-example2.json || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim host-add $DIRvim/test/hosts/host-example3.json || ! echo "fail" >&2 || $_exit 1
+ echo "Adding example nets"
+ ${DIRvim}/openvim net-create $DIRvim/test/networks/net-example0.yaml || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim net-create $DIRvim/test/networks/net-example1.yaml || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim net-create $DIRvim/test/networks/net-example2.yaml || ! echo "fail" >&2 || $_exit 1
+ ${DIRvim}/openvim net-create $DIRvim/test/networks/net-example3.yaml || ! echo "fail" >&2 || $_exit 1
+
+ printf "%-50s" "Creating openvim tenant 'TEST-admin': "
+ result=`openvim tenant-create '{"tenant": {"name":"TEST-admin", "description":"admin"}}'`
+ vimtenant=`echo $result |gawk '{print $1}'`
+ #check a valid uuid is obtained
+ ! is_valid_uuid $vimtenant && echo "FAIL" && echo " $result" && $_exit 1
+ echo " $vimtenant"
+ export OPENVIM_TENANT=$vimtenant
+ [[ $insert_bashrc == y ]] && echo -e "\nexport OPENVIM_TENANT=$vimtenant" >> ~/.bashrc
+
+ echo
+ #echo "Check virtual machines are deployed"
+ #vms_error=`openvim vm-list | grep ERROR | wc -l`
+ #vms=`openvim vm-list | wc -l`
+ #[[ $vms -ne 8 ]] && echo "WARNING: $vms VMs created, must be 8 VMs" >&2 && $_exit 1
+ #[[ $vms_error -gt 0 ]] && echo "WARNING: $vms_error VMs with ERROR" >&2 && $_exit 1
+fi
+done
+
+echo
+echo DONE
+#echo "Listing VNFs"
+#openvim vnf-list
+#echo "Listing scenarios"
+#openvim scenario-list
+#echo "Listing scenario instances"
+#openvim instance-scenario-list
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#ONLY TESTED for Ubuntu 14.10 14.04, CentOS7 and RHEL7
+#Get needed packages, to run floodlight
+
+function usage(){
+ echo -e "usage: sudo $0 \n Install floodlight v0.9 in ./floodlight-0.90"
+}
+
+function install_packages(){
+ [ -x /usr/bin/apt-get ] && apt-get install -y $*
+ [ -x /usr/bin/yum ] && yum install -y $*
+
+ #check properly installed
+ for PACKAGE in $*
+ do
+ PACKAGE_INSTALLED="no"
+ [ -x /usr/bin/apt-get ] && dpkg -l $PACKAGE &>> /dev/null && PACKAGE_INSTALLED="yes"
+ [ -x /usr/bin/yum ] && yum list installed $PACKAGE &>> /dev/null && PACKAGE_INSTALLED="yes"
+ if [ "$PACKAGE_INSTALLED" = "no" ]
+ then
+ echo "failed to install package '$PACKAGE'. Revise network connectivity and try again"
+ exit -1
+ fi
+ done
+}
+
+#check root privileges and non a root user behind
+[ "$1" == "-h" -o "$1" == "--help" ] && usage && exit 0
+[ "$USER" != "root" ] && echo "Needed root privileges" >&2 && usage >&2 && exit -1
+[ -z "$SUDO_USER" -o "$SUDO_USER" = "root" ] && echo "Must be runned with sudo from a non root user" >&2 && usage >&2 && exit -1
+
+echo "This script will update repositories and Installing FloodLight."
+echo "It will install Java and other packages, that takes a while to download"
+read -e -p "Do you agree on download and install FloodLight from http://www.projectfloodlight.org upon the owner license? (y/N)" KK
+[[ "$KK" != "y" ]] && [[ "$KK" != "yes" ]] && exit 0
+
+#Discover Linux distribution
+#try redhat type
+[ -f /etc/redhat-release ] && _DISTRO=$(cat /etc/redhat-release 2>/dev/null | cut -d" " -f1)
+#if not assuming ubuntu type
+[ -f /etc/redhat-release ] || _DISTRO=$(lsb_release -is 2>/dev/null)
+if [ "$_DISTRO" == "Ubuntu" ]
+then
+ _RELEASE="14"
+ if ! lsb_release -rs | grep -q "14."
+ then
+ read -e -p "WARNING! Not tested Ubuntu version. Continue assuming a '$_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+elif [ "$_DISTRO" == "CentOS" ]
+then
+ _RELEASE="7"
+ if ! cat /etc/redhat-release | grep -q "7."
+ then
+ read -e -p "WARNING! Not tested CentOS version. Continue assuming a '_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+elif [ "$_DISTRO" == "Red" ]
+then
+ _RELEASE="7"
+ if ! cat /etc/redhat-release | grep -q "7."
+ then
+ read -e -p "WARNING! Not tested Red Hat OS version. Continue assuming a '_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+else #[ "$_DISTRO" != "Ubuntu" -a "$_DISTRO" != "CentOS" -a "$_DISTRO" != "Red" ]
+ _DISTRO_DISCOVER=$_DISTRO
+ [ -x /usr/bin/apt-get ] && _DISTRO="Ubuntu" && _RELEASE="14"
+ [ -x /usr/bin/yum ] && _DISTRO="CentOS" && _RELEASE="7"
+ read -e -p "WARNING! Not tested Linux distribution '$_DISTRO_DISCOVER '. Continue assuming a '$_DISTRO $_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+fi
+
+
+
+echo '
+#################################################################
+##### UPDATE REPOSITORIES #####
+#################################################################'
+[ "$_DISTRO" == "Ubuntu" ] && apt-get update -y
+
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && yum check-update -y
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && sudo yum repolist
+
+echo '
+#################################################################
+##### DOWNLOADING AND CONFIGURE FLOODLIGHT #####
+#################################################################'
+ #Install Java JDK and Ant packages at the VM
+ [ "$_DISTRO" == "Ubuntu" ] && install_packages "build-essential default-jdk ant python-dev screen wget" #TODO revise if packages are needed apart from ant
+ [ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && install_package " ant screen wget"
+
+ #floodlight 0.9
+ echo "downloading v0.90 from the oficial page"
+ su $SUDO_USER -c 'wget https://github.com/floodlight/floodlight/archive/v0.90.tar.gz'
+ su $SUDO_USER -c 'tar xvzf v0.90.tar.gz'
+ #floodlight 1.1
+ #echo "downloading v1.1 from the oficial page"
+ #su $SUDO_USER -c 'wget https://github.com/floodlight/floodlight/archive/v1.1.tar.gz'
+ #su $SUDO_USER -c 'tar xvzf v01.1.tar.gz'
+
+ #Configure Java environment variables. It is seem that is not needed!!!
+ #export JAVA_HOME=/usr/lib/jvm/default-java" >> /home/${SUDO_USER}/.bashr
+ #export PATH=$PATH:$JAVA_HOME
+ #echo "export JAVA_HOME=/usr/lib/jvm/default-java" >> /home/${SUDO_USER}/.bashrc
+ #echo "export PATH=$PATH:$JAVA_HOME" >> /home/${SUDO_USER}/.bashrc
+
+ #Compile floodlight
+ pushd ./floodlight-0.90
+ #pushd ./floodlight-1.1
+ su $SUDO_USER -c 'ant'
+ export FLOODLIGHT_PATH=$(pwd)
+ popd
+
+echo '
+#################################################################
+##### CONFIGURE envioronment #####
+#################################################################'
+#insert into .bashrc
+ echo " inserting FLOODLIGHT_PATH at .bashrc"
+ su $SUDO_USER -c "echo 'export FLOODLIGHT_PATH=\"${FLOODLIGHT_PATH}\"' >> ~/.bashrc"
+
+echo
+echo "Done! you may need to logout and login again for loading the configuration"
+echo " If your have installed openvim, run './openvim/scripts/service-floodlight.sh start' for starting floodlight in a screen"
+
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#ONLY TESTED for Ubuntu 14.10 14.04, CentOS7 and RHEL7
+#Get needed packages, source code and configure to run openvim
+#Ask for database user and password if not provided
+# $1: database user
+# $2: database password
+
+function usage(){
+ echo -e "usage: sudo $0 [db-user [db-passwd]]\n Install source code in ./openvim"
+}
+
+function install_packages(){
+ [ -x /usr/bin/apt-get ] && apt-get install -y $*
+ [ -x /usr/bin/yum ] && yum install -y $*
+
+ #check properly installed
+ for PACKAGE in $*
+ do
+ PACKAGE_INSTALLED="no"
+ [ -x /usr/bin/apt-get ] && dpkg -l $PACKAGE &>> /dev/null && PACKAGE_INSTALLED="yes"
+ [ -x /usr/bin/yum ] && yum list installed $PACKAGE &>> /dev/null && PACKAGE_INSTALLED="yes"
+ if [ "$PACKAGE_INSTALLED" = "no" ]
+ then
+ echo "failed to install package '$PACKAGE'. Revise network connectivity and try again"
+ exit -1
+ fi
+ done
+}
+
+#check root privileges and non a root user behind
+[ "$1" == "-h" -o "$1" == "--help" ] && usage && exit 0
+[ "$USER" != "root" ] && echo "Needed root privileges" >&2 && usage >&2 && exit -1
+[ -z "$SUDO_USER" -o "$SUDO_USER" = "root" ] && echo "Must be runned with sudo from a non root user" >&2 && usage >&2 && exit -1
+
+
+#Discover Linux distribution
+#try redhat type
+[ -f /etc/redhat-release ] && _DISTRO=$(cat /etc/redhat-release 2>/dev/null | cut -d" " -f1)
+#if not assuming ubuntu type
+[ -f /etc/redhat-release ] || _DISTRO=$(lsb_release -is 2>/dev/null)
+if [ "$_DISTRO" == "Ubuntu" ]
+then
+ _RELEASE="14"
+ if ! lsb_release -rs | grep -q "14."
+ then
+ read -e -p "WARNING! Not tested Ubuntu version. Continue assuming a '$_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+elif [ "$_DISTRO" == "CentOS" ]
+then
+ _RELEASE="7"
+ if ! cat /etc/redhat-release | grep -q "7."
+ then
+ read -e -p "WARNING! Not tested CentOS version. Continue assuming a '_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+elif [ "$_DISTRO" == "Red" ]
+then
+ _RELEASE="7"
+ if ! cat /etc/redhat-release | grep -q "7."
+ then
+ read -e -p "WARNING! Not tested Red Hat OS version. Continue assuming a '_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+ fi
+else #[ "$_DISTRO" != "Ubuntu" -a "$_DISTRO" != "CentOS" -a "$_DISTRO" != "Red" ]
+ _DISTRO_DISCOVER=$_DISTRO
+ [ -x /usr/bin/apt-get ] && _DISTRO="Ubuntu" && _RELEASE="14"
+ [ -x /usr/bin/yum ] && _DISTRO="CentOS" && _RELEASE="7"
+ read -e -p "WARNING! Not tested Linux distribution '$_DISTRO_DISCOVER '. Continue assuming a '$_DISTRO $_RELEASE' type? (y/N)" KK
+ [ "$KK" != "y" -a "$KK" != "yes" ] && echo "Cancelled" && exit 0
+fi
+
+
+
+echo '
+#################################################################
+##### UPDATE REPOSITORIES #####
+#################################################################'
+[ "$_DISTRO" == "Ubuntu" ] && apt-get update -y
+
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && yum check-update -y
+[ "$_DISTRO" == "CentOS" ] && sudo yum install -y epel-release
+[ "$_DISTRO" == "Red" ] && wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm \
+ && sudo rpm -ivh epel-release-7-5.noarch.rpm && sudo yum install -y epel-release && rm -f epel-release-7-5.noarch.rpm
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && sudo yum repolist
+
+
+echo '
+#################################################################
+##### INSTALL REQUIRED PACKAGES #####
+#################################################################'
+[ "$_DISTRO" == "Ubuntu" ] && install_packages "mysql-server"
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && install_packages "mariadb mariadb-server"
+
+if [ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ]
+then
+ #start services. By default CentOS does not start services
+ service mariadb start
+ service httpd start
+ systemctl enable mariadb
+ systemctl enable httpd
+ read -e -p "Do you want to configure mariadb (recomended if not done before) (Y/n)" KK
+ [ "$KK" != "n" -a "$KK" != "no" ] && mysql_secure_installation
+
+ read -e -p "Do you want to set firewall to grant web access port 80,443 (Y/n)" KK
+ [ "$KK" != "n" -a "$KK" != "no" ] &&
+ firewall-cmd --permanent --zone=public --add-service=http &&
+ firewall-cmd --permanent --zone=public --add-service=https &&
+ firewall-cmd --reload
+fi
+
+#check and ask for database user password. Must be done after database instalation
+[ -n "$1" ] && DBUSER=$1
+[ -z "$1" ] && DBUSER=root
+[ -n "$2" ] && DBPASSWD="-p$2"
+[ -z "$2" ] && DBPASSWD=""
+echo -e "\nCheking database connection and ask for credentials"
+while ! echo "" | mysql -u$DBUSER $DBPASSWD
+do
+ [ -n "$logintry" ] && echo -e "\nInvalid database credentials!!!. Try again (Ctrl+c to abort)"
+ [ -z "$logintry" ] && echo -e "\nProvide database credentials"
+ read -e -p "database user? ($DBUSER) " DBUSER_
+ [ -n "$DBUSER_" ] && DBUSER=$DBUSER_
+ read -e -s -p "database password? (Enter for not using password) " DBPASSWD_
+ [ -n "$DBPASSWD_" ] && DBPASSWD="-p$DBPASSWD_"
+ [ -z "$DBPASSWD_" ] && DBPASSWD=""
+ logintry="yes"
+done
+
+echo '
+#################################################################
+##### INSTALL PYTHON PACKAGES #####
+#################################################################'
+[ "$_DISTRO" == "Ubuntu" ] && install_packages "python-yaml python-libvirt python-bottle python-mysqldb python-jsonschema python-paramiko python-argcomplete python-requests git screen wget"
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && install_packages "PyYAML libvirt-python MySQL-python python-jsonschema python-paramiko python-argcomplete python-requests git screen wget"
+
+#The only way to install python-bottle on Centos7 is with easy_install or pip
+[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && easy_install -U bottle
+
+echo '
+#################################################################
+##### DOWNLOAD SOURCE #####
+#################################################################'
+su $SUDO_USER -c 'git clone https://github.com/nfvlabs/openvim.git openvim'
+#Unncoment to use a concrete branch, if not main branch
+#pushd openvim
+#su $SUDO_USER -c 'git checkout v0.4'
+#popd
+
+echo '
+#################################################################
+##### CREATE DATABASE #####
+#################################################################'
+mysqladmin -u$DBUSER $DBPASSWD create vim_db
+
+echo "CREATE USER 'vim'@'localhost' identified by 'vimpw';" | mysql -u$DBUSER $DBPASSWD
+echo "GRANT ALL PRIVILEGES ON vim_db.* TO 'vim'@'localhost';" | mysql -u$DBUSER $DBPASSWD
+
+echo "vim database"
+su $SUDO_USER -c './openvim/database_utils/init_vim_db.sh -u vim -p vimpw'
+
+
+if [ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ]
+then
+ echo '
+#################################################################
+##### CONFIGURE firewalld #####
+#################################################################'
+ read -e -p "Configure firewalld for openvimd port 9080? (Y/n)" KK
+ if [ "$KK" != "n" -a "$KK" != "no" ]
+ then
+ #Creates a service file for openvim
+ echo '<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>openvimd</short>
+ <description>openvimd service</description>
+ <port protocol="tcp" port="9080"/>
+</service>' > /etc/firewalld/services/openvimd.xml
+ #put proper permissions
+ pushd /etc/firewalld/services > /dev/null
+ restorecon openvim
+ chmod 640 openvim
+ popd > /dev/null
+ #Add the openvim service to the default zone permanently and reload the firewall configuration
+ firewall-cmd --permanent --add-service=openvim > /dev/null
+ firewall-cmd --reload > /dev/null
+ echo "done."
+ else
+ echo "skipping."
+ fi
+fi
+
+echo '
+#################################################################
+##### CONFIGURE openvim CLIENTS #####
+#################################################################'
+#creates a link at ~/bin
+su $SUDO_USER -c 'mkdir -p ~/bin'
+rm -f /home/${SUDO_USER}/bin/openvim
+rm -f /home/${SUDO_USER}/bin/openflow
+rm -f /home/${SUDO_USER}/bin/service-openvim
+rm -f /home/${SUDO_USER}/bin/initopenvim
+rm -f /home/${SUDO_USER}/bin/service-floodlight
+rm -f /home/${SUDO_USER}/bin/service-opendaylight
+rm -f /home/${SUDO_USER}/bin/get_dhcp_lease.sh
+ln -s ${PWD}/openvim/openvim /home/${SUDO_USER}/bin/openvim
+ln -s ${PWD}/openvim/openflow /home/${SUDO_USER}/bin/openflow
+ln -s ${PWD}/openvim/scripts/service-openvim.sh /home/${SUDO_USER}/bin/service-openvim
+ln -s ${PWD}/openvim/scripts/initopenvim.sh /home/${SUDO_USER}/bin/initopenvim
+ln -s ${PWD}/openvim/scripts/service-floodlight.sh /home/${SUDO_USER}/bin/service-floodlight
+ln -s ${PWD}/openvim/scripts/service-opendaylight.sh /home/${SUDO_USER}/bin/service-opendaylight
+ln -s ${PWD}/openvim/scripts/get_dhcp_lease.sh /home/${SUDO_USER}/bin/get_dhcp_lease.sh
+
+#insert /home/<user>/bin in the PATH
+#skiped because normally this is done authomatically when ~/bin exist
+#if ! su $SUDO_USER -c 'echo $PATH' | grep -q "/home/${SUDO_USER}/bin"
+#then
+# echo " inserting /home/$SUDO_USER/bin in the PATH at .bashrc"
+# su $SUDO_USER -c 'echo "PATH=\$PATH:/home/\${USER}/bin" >> ~/.bashrc'
+#fi
+
+#configure arg-autocomplete for this user
+#in case of minmal instalation this package is not installed by default
+[[ "$_DISTRO" == "CentOS" || "$_DISTRO" == "Red" ]] && yum install -y bash-completion
+#su $SUDO_USER -c 'mkdir -p ~/.bash_completion.d'
+su $SUDO_USER -c 'activate-global-python-argcomplete --user'
+if ! grep -q bash_completion.d/python-argcomplete.sh /home/${SUDO_USER}/.bashrc
+then
+ echo " inserting .bash_completion.d/python-argcomplete.sh execution at .bashrc"
+ su $SUDO_USER -c 'echo ". /home/${USER}/.bash_completion.d/python-argcomplete.sh" >> ~/.bashrc'
+fi
+
+echo
+echo "Done! you may need to logout and login again for loading the configuration"
+echo " Run './openvim/scripts/service-openvim.sh start' for starting openvim in a screen"
+
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#It generates a report for debugging
+
+DIRNAME=$(readlink -f ${BASH_SOURCE[0]})
+DIRNAME=$(dirname $DIRNAME )
+OVCLIENT=$DIRNAME/../openvim
+
+#get screen log files at the beginning
+echo
+echo "-------------------------------"
+echo "log files"
+echo "-------------------------------"
+echo
+echo "cat $DIRNAME/../logs/openvim.log*"
+cat $DIRNAME/../logs/openvim.log*
+echo
+echo
+
+#get version
+echo
+echo "-------------------------------"
+echo "version"
+echo "-------------------------------"
+echo "cat $DIRNAME/../openvimd.py|grep ^__version__"
+cat $DIRNAME/../openvimd.py|grep ^__version__
+echo
+echo
+
+#get configuration files
+echo "-------------------------------"
+echo "Configuration files"
+echo "-------------------------------"
+echo "cat $DIRNAME/../openvimd.cfg"
+cat $DIRNAME/../openvimd.cfg
+echo
+
+#get list of items
+for verbose in "" "-vvv"
+do
+ echo "-------------------------------"
+ echo "OPENVIM$verbose"
+ echo "-------------------------------"
+ echo "$OVCLIENT config"
+ $OVCLIENT config
+ echo "-------------------------------"
+ echo "$OVCLIENT tenant-list $verbose"
+ $OVCLIENT tenant-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT host-list $verbose"
+ $OVCLIENT host-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT net-list $verbose"
+ $OVCLIENT net-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT port-list $verbose"
+ $OVCLIENT port-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT flavor-list $verbose"
+ $OVCLIENT flavor-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT image-list $verbose"
+ $OVCLIENT image-list $verbose
+ echo "-------------------------------"
+ echo "$OVCLIENT vm-list $verbose"
+ $OVCLIENT vm-list $verbose
+ echo "-------------------------------"
+ echo
+
+done
+echo
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#launch floodlight inside a screen. It assumes shell variable $FLOODLIGHT_PATH
+# contain the installation path
+
+
+DIRNAME=$(readlink -f ${BASH_SOURCE[0]})
+DIRNAME=$(dirname $DIRNAME )
+DIR_OM=$(dirname $DIRNAME )
+
+function usage(){
+ echo -e "Usage: $0 start|stop|restart|status"
+ echo -e " Launch|Removes|Restart|Getstatus floodlight on a screen"
+ echo -e " Shell variable FLOODLIGHT_PATH must indicate floodlight installationpath"
+}
+
+function kill_pid(){
+ #send TERM signal and wait 5 seconds and send KILL signal ir still running
+ #PARAMS: $1: PID of process to terminate
+ kill $1 #send TERM signal
+ WAIT=5
+ while [ $WAIT -gt 0 ] && ps -o pid -U $USER -u $USER | grep -q $1
+ do
+ sleep 1
+ WAIT=$((WAIT-1))
+ [ $WAIT -eq 0 ] && echo -n "sending SIGKILL... " && kill -9 $1 #kill when count reach 0
+ done
+ echo "done"
+
+}
+
+#obtain parameters
+#om_action="start" #uncoment to get a default action
+for param in $*
+do
+ [ "$param" == "start" -o "$param" == "stop" -o "$param" == "restart" -o "$param" == "status" ] && om_action=$param && continue
+ [ "$param" == "openflow" -o "$param" == "flow" -o "$param" == "floodlight" ] && continue
+ [ "$param" == "-h" -o "$param" == "--help" ] && usage && exit 0
+
+ #if none of above, reach this line because a param is incorrect
+ echo "Unknown param '$param' type $0 --help" >&2
+ exit -1
+done
+
+#check action is provided
+[ -z "$om_action" ] && usage >&2 && exit -1
+
+ om_cmd="floodlight.jar"
+ om_name="floodlight"
+
+ #obtain PID of program
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+
+ #status
+ if [ "$om_action" == "status" ]
+ then
+ [ -n "$component_id" ] && echo " $om_name running, pid $component_id"
+ [ -z "$component_id" ] && echo " $om_name stopped"
+ fi
+
+ #stop
+ if [ "$om_action" == "stop" -o "$om_action" == "restart" ]
+ then
+ #terminates program
+ [ -n "$component_id" ] && echo -n " stopping $om_name ... " && kill_pid $component_id
+ component_id=""
+ #terminates screen
+ if screen -wipe | grep -Fq .flow
+ then
+ screen -S flow -p 0 -X stuff "exit\n"
+ sleep 1
+ fi
+ fi
+
+ #start
+ if [ "$om_action" == "start" -o "$om_action" == "restart" ]
+ then
+ [[ -z $FLOODLIGHT_PATH ]] && echo "FLOODLIGHT_PATH shell variable must indicate floodlight installation path" >&2 && exit -1
+ #calculates log file name
+ logfile=""
+ mkdir -p $DIR_OM/logs && logfile=$DIR_OM/logs/openflow.log || echo "can not create logs directory $DIR_OM/logs"
+ #check already running
+ [ -n "$component_id" ] && echo " $om_name is already running. Skipping" && continue
+ #create screen if not created
+ echo -n " starting $om_name ... "
+ if ! screen -wipe | grep -Fq .flow
+ then
+ pushd ${FLOODLIGHT_PATH} > /dev/null
+ screen -dmS flow bash
+ sleep 1
+ popd > /dev/null
+ else
+ echo -n " using existing screen 'flow' ... "
+ screen -S flow -p 0 -X log off
+ screen -S flow -p 0 -X stuff "cd ${FLOODLIGHT_PATH}\n"
+ sleep 1
+ fi
+ #move old log file index one number up and log again in index 0
+ if [[ -n $logfile ]]
+ then
+ for index in 8 7 6 5 4 3 2 1
+ do
+ [[ -f ${logfile}.${index} ]] && mv ${logfile}.${index} ${logfile}.$((index+1))
+ done
+ [[ -f ${logfile} ]] && mv ${logfile} ${logfile}.1
+ screen -S flow -p 0 -X logfile ${logfile}
+ screen -S flow -p 0 -X log on
+ fi
+ #launch command to screen
+ screen -S flow -p 0 -X stuff "java -Dlogback.configurationFile=${DIRNAME}/flow-logback.xml -jar ./target/floodlight.jar -cf ${DIRNAME}/flow.properties_v0.9\n"
+ #check if is running
+ [[ -n $logfile ]] && timeout=120 #2 minute
+ [[ -z $logfile ]] && timeout=20
+ while [[ $timeout -gt 0 ]]
+ do
+ #check if is running
+ #echo timeout $timeout
+ #if ! ps -f -U $USER -u $USER | grep -v grep | grep -q ${om_cmd}
+ log_lines=0
+ [[ -n $logfile ]] && log_lines=`head ${logfile} | wc -l`
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+ if [[ -z $component_id ]]
+ then #process not started or finished
+ [[ $log_lines -ge 2 ]] && echo -n "ERROR, it has exited." && break
+ #started because writted serveral lines at log so report error
+ fi
+ [[ -n $logfile ]] && grep -q "Listening for switch connections" ${logfile} && sleep 1 && break
+ sleep 1
+ timeout=$((timeout -1))
+ done
+ if [[ -n $logfile ]] && [[ $timeout == 0 ]]
+ then
+ echo -n "timeout!"
+ else
+ echo -n "running on 'screen -x flow'."
+ fi
+ [[ -n $logfile ]] && echo " Logging at '${logfile}'" || echo
+ fi
+
+
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#launch opendaylight inside a screen. It assumes shell variable $OPENDAYLIGHT_PATH
+# contain the installation path
+
+
+DIRNAME=$(readlink -f ${BASH_SOURCE[0]})
+DIRNAME=$(dirname $DIRNAME )
+DIR_OM=$(dirname $DIRNAME )
+
+function usage(){
+ echo -e "Usage: $0 start|stop|restart|status"
+ echo -e " Launch|Removes|Restart|Getstatus opendaylight on a screen"
+ echo -e " Shell variable OPENDAYDLIGHT_PATH must indicate opendaylight installation path"
+}
+
+function kill_pid(){
+ #send TERM signal and wait 5 seconds and send KILL signal ir still running
+ #PARAMS: $1: PID of process to terminate
+ kill $1 #send TERM signal
+ WAIT=5
+ while [ $WAIT -gt 0 ] && ps -o pid -U $USER -u $USER | grep -q $1
+ do
+ sleep 1
+ WAIT=$((WAIT-1))
+ [ $WAIT -eq 0 ] && echo -n "sending SIGKILL... " && kill -9 $1 #kill when count reach 0
+ done
+ echo "done"
+
+}
+
+#obtain parameters
+#om_action="start" #uncoment to get a default action
+for param in $*
+do
+ [ "$param" == "start" -o "$param" == "stop" -o "$param" == "restart" -o "$param" == "status" ] && om_action=$param && continue
+ [ "$param" == "openflow" -o "$param" == "flow" -o "$param" == "opendaylight" ] && continue
+ [ "$param" == "-h" -o "$param" == "--help" ] && usage && exit 0
+
+ #if none of above, reach this line because a param is incorrect
+ echo "Unknown param '$param' type $0 --help" >&2
+ exit -1
+done
+
+#check action is provided
+[ -z "$om_action" ] && usage >&2 && exit -1
+
+ om_cmd="./karaf"
+ om_name="opendaylight"
+
+ #obtain PID of program
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+
+ #status
+ if [ "$om_action" == "status" ]
+ then
+ [ -n "$component_id" ] && echo " $om_name running, pid $component_id"
+ [ -z "$component_id" ] && echo " $om_name stopped"
+ fi
+
+ #stop
+ if [ "$om_action" == "stop" -o "$om_action" == "restart" ]
+ then
+ #terminates program
+ [ -n "$component_id" ] && echo -n " stopping $om_name ... " && kill_pid $component_id
+ component_id=""
+ #terminates screen
+ if screen -wipe | grep -Fq .flow
+ then
+ screen -S flow -p 0 -X stuff "exit\n"
+ sleep 1
+ fi
+ fi
+
+ #start
+ if [ "$om_action" == "start" -o "$om_action" == "restart" ]
+ then
+ [[ -z $OPENDAYDLIGHT_PATH ]] && echo "OPENDAYDLIGHT_PATH shell variable must indicate opendaylight installation path" >&2 && exit -1
+ #calculates log file name
+ logfile=""
+ mkdir -p $DIR_OM/logs && logfile=$DIR_OM/logs/openflow.log && logfile_console=$DIR_OM/logs/openflow_console.log || echo "can not create logs directory $DIR_OM/logs"
+ #check already running
+ [ -n "$component_id" ] && echo " $om_name is already running. Skipping" && continue
+ #create screen if not created
+ echo -n " starting $om_name ... "
+ if ! screen -wipe | grep -Fq .flow
+ then
+ pushd ${OPENDAYDLIGHT_PATH}/bin > /dev/null
+ screen -dmS flow bash
+ sleep 1
+ popd > /dev/null
+ else
+ echo -n " using existing screen 'flow' ... "
+ screen -S flow -p 0 -X log off
+ screen -S flow -p 0 -X stuff "cd ${OPENDAYDLIGHT_PATH}/bin\n"
+ sleep 1
+ fi
+ #move old log file index one number up and log again in index 0
+ if [[ -n $logfile ]]
+ then
+ for index in .9 .8 .7 .6 .5 .4 .3 .2 .1 ""
+ do
+ rm -f ${logfile}${index}
+ ln -s ${OPENDAYDLIGHT_PATH}/data/log/karaf.log${index} ${logfile}${index}
+ done
+ rm -rf ${logfile_console}
+ screen -S flow -p 0 -X logfile ${logfile_console}
+ screen -S flow -p 0 -X log on
+ fi
+ #launch command to screen
+ screen -S flow -p 0 -X stuff "${om_cmd}\n"
+ #check if is running
+ [[ -n $logfile ]] && timeout=120 #2 minute
+ [[ -z $logfile ]] && timeout=20
+ while [[ $timeout -gt 0 ]]
+ do
+ #check if is running
+ #echo timeout $timeout
+ #if ! ps -f -U $USER -u $USER | grep -v grep | grep -q ${om_cmd}
+ log_lines=0
+ [[ -n $logfile_console ]] && log_lines=`head ${logfile_console} | wc -l`
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+ if [[ -z $component_id ]]
+ then #process not started or finished
+ [[ $log_lines -ge 2 ]] && echo -n "ERROR, it has exited." && break
+ #started because writted serveral lines at log so report error
+ fi
+ [[ -n $logfile_console ]] && grep -q "Listening on port" ${logfile_console} && sleep 1 && break
+ sleep 1
+ timeout=$((timeout -1))
+ done
+ if [[ -n $logfile_console ]] && [[ $timeout == 0 ]]
+ then
+ echo -n "timeout!"
+ else
+ echo -n "running on 'screen -x flow'."
+ fi
+ [[ -n $logfile ]] && echo " Logging at '${logfile}'" || echo
+ fi
+
+
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#launch openvim (andr floodlight) inside a screen.
+#It assumes a relative path '..' for openvim
+#for floodlight, the variable FLOODLIGHT_PATH indicates the installation path
+
+
+DIRNAME=$(readlink -f ${BASH_SOURCE[0]})
+DIRNAME=$(dirname $DIRNAME )
+DIR_OM=$(dirname $DIRNAME )
+#[[ -z $FLOODLIGHT_PATH ]] && FLOODLIGHT_PATH=$(dirname ${DIR_OM})/floodlight-1.1
+#[[ -z $FLOODLIGHT_PATH ]] && FLOODLIGHT_PATH=$(dirname ${DIR_OM})/floodlight-0.90
+
+function usage(){
+ echo -e "Usage: $0 [openvim/vim] [floodlight/flow] start|stop|restart|status"
+ echo -e " Launch|Removes|Restart|Getstatus openvim (by default) or/and floodlight on a screen"
+ echo -e " For floodlight variable FLOODLIGHT_PATH must indicate installation path"
+}
+
+function kill_pid(){
+ #send TERM signal and wait 5 seconds and send KILL signal ir still running
+ #PARAMS: $1: PID of process to terminate
+ kill $1 #send TERM signal
+ WAIT=5
+ while [ $WAIT -gt 0 ] && ps -o pid -U $USER -u $USER | grep -q $1
+ do
+ sleep 1
+ WAIT=$((WAIT-1))
+ [ $WAIT -eq 0 ] && echo -n "sending SIGKILL... " && kill -9 $1 #kill when count reach 0
+ done
+ echo "done"
+
+}
+
+#obtain parameters
+om_list=""
+#om_action="start" #uncoment to get a default action
+for param in $*
+do
+ [ "$param" == "start" -o "$param" == "stop" -o "$param" == "restart" -o "$param" == "status" ] && om_action=$param && continue
+ [ "$param" == "openvim" -o "$param" == "vim" ] && om_list="$om_list vim" && continue
+ [ "$param" == "openmano" -o "$param" == "mano" ] && continue #allow and ingore for backwards compatibility
+ [ "$param" == "openflow" -o "$param" == "flow" -o "$param" == "floodlight" ] && om_list="flow $om_list" && continue
+ [ "$param" == "-h" -o "$param" == "--help" ] && usage && exit 0
+ #note flow that it must be the first element, because openvim relay on this
+
+ #if none of above, reach this line because a param is incorrect
+ echo "Unknown param '$param' type $0 --help" >&2
+ exit -1
+done
+
+#check action is provided
+[ -z "$om_action" ] && usage >&2 && exit -1
+
+#if no componenets supplied assume all
+[ -z "$om_list" ] && om_list="vim"
+
+for om_component in $om_list
+do
+ [ "${om_component}" == "flow" ] && om_cmd="floodlight.jar" && om_name="floodlight" && om_dir=$FLOODLIGHT_PATH
+ [ "${om_component}" == "vim" ] && om_cmd="openvimd.py" && om_name="openvim " && om_dir=${DIR_OM}
+ #obtain PID of program
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+
+ #status
+ if [ "$om_action" == "status" ]
+ then
+ [ -n "$component_id" ] && echo " $om_name running, pid $component_id"
+ [ -z "$component_id" ] && echo " $om_name stopped"
+ fi
+
+ #stop
+ if [ "$om_action" == "stop" -o "$om_action" == "restart" ]
+ then
+ #terminates program
+ [ -n "$component_id" ] && echo -n " stopping $om_name ... " && kill_pid $component_id
+ component_id=""
+ #terminates screen
+ if screen -wipe | grep -Fq .$om_component
+ then
+ screen -S $om_component -p 0 -X stuff "exit\n"
+ sleep 1
+ fi
+ fi
+
+ #start
+ if [ "$om_action" == "start" -o "$om_action" == "restart" ]
+ then
+ [[ -z $FLOODLIGHT_PATH ]] && [[ $om_component == flow ]] &&
+ echo "FLOODLIGHT_PATH shell variable must indicate floodlight installation path" >&2 && exit -1
+ #calculates log file name
+ logfile=""
+ mkdir -p $DIR_OM/logs && logfile=$DIR_OM/logs/open${om_component}.log || echo "can not create logs directory $DIR_OM/logs"
+ #check already running
+ [ -n "$component_id" ] && echo " $om_name is already running. Skipping" && continue
+ #create screen if not created
+ echo -n " starting $om_name ... "
+ if ! screen -wipe | grep -Fq .${om_component}
+ then
+ pushd ${om_dir} > /dev/null
+ screen -dmS ${om_component} bash
+ sleep 1
+ popd > /dev/null
+ else
+ echo -n " using existing screen '${om_component}' ... "
+ screen -S ${om_component} -p 0 -X log off
+ screen -S ${om_component} -p 0 -X stuff "cd ${om_dir}\n"
+ sleep 1
+ fi
+ #move old log file index one number up and log again in index 0
+ if [[ -n $logfile ]]
+ then
+ for index in 8 7 6 5 4 3 2 1
+ do
+ [[ -f ${logfile}.${index} ]] && mv ${logfile}.${index} ${logfile}.$((index+1))
+ done
+ [[ -f ${logfile} ]] && mv ${logfile} ${logfile}.1
+ screen -S ${om_component} -p 0 -X logfile ${logfile}
+ screen -S ${om_component} -p 0 -X log on
+ fi
+ #launch command to screen
+ #[ "${om_component}" != "flow" ] && screen -S ${om_component} -p 0 -X stuff "cd ${DIR_OM}/open${om_component}\n" && sleep 1
+ [ "${om_component}" == "flow" ] && screen -S flow -p 0 -X stuff "java -Dlogback.configurationFile=${DIRNAME}/flow-logback.xml -jar ./target/floodlight.jar -cf ${DIRNAME}/flow.properties_v0.9\n"
+ #[ "${om_component}" == "flow" ] && screen -S flow -p 0 -X stuff "java -Dlogback.configurationFile=${DIRNAME}/flow-logback.xml -jar ./target/floodlight.jar -cf ${DIRNAME}/flow.properties_v1.1\n" && sleep 5
+ [ "${om_component}" != "flow" ] && screen -S ${om_component} -p 0 -X stuff "./${om_cmd}\n"
+ #check if is running
+ [[ -n $logfile ]] && timeout=120 #2 minute
+ [[ -z $logfile ]] && timeout=20
+ while [[ $timeout -gt 0 ]]
+ do
+ #check if is running
+ #echo timeout $timeout
+ #if ! ps -f -U $USER -u $USER | grep -v grep | grep -q ${om_cmd}
+ log_lines=0
+ [[ -n $logfile ]] && log_lines=`head ${logfile} | wc -l`
+ component_id=`ps -o pid,cmd -U $USER -u $USER | grep -v grep | grep ${om_cmd} | awk '{print $1}'`
+ if [[ -z $component_id ]]
+ then #process not started or finished
+ [[ $log_lines -ge 2 ]] && echo -n "ERROR, it has exited." && break
+ #started because writted serveral lines at log so report error
+ fi
+ [[ -n $logfile ]] && [[ ${om_component} == flow ]] && grep -q "Listening for switch connections" ${logfile} && sleep 1 && break
+ [[ -n $logfile ]] && [[ ${om_component} != flow ]] && grep -q "open${om_component}d ready" ${logfile} && break
+ sleep 1
+ timeout=$((timeout -1))
+ done
+ if [[ -n $logfile ]] && [[ $timeout == 0 ]]
+ then
+ echo -n "timeout!"
+ else
+ echo -n "running on 'screen -x ${om_component}'."
+ fi
+ [[ -n $logfile ]] && echo " Logging at '${logfile}'" || echo
+ fi
+done
+
+
+
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+flavor:
+ name: flavor-name
+ description: flavor-description
+ # cloud type requirements
+ ram: 1024 # Memory in MB. Ignored if provided 'memory' at 'extended'
+ vcpus: 2 # Number of cpus. Ignored if provided at 'extended'
+
+ # NFV type requirements
+ # allocating EXCLUSIVE resoureces in the same NUMA node.
+ extended: # optional
+ processor_ranking: 100 # minimal processor family. Not used in current version
+ numas: # list of numa set. Only one supported in current version
+ - memory: 8 # GByte of huge pages at this numa
+
+ #Choose among one of "cores", "paired-threads", "threads"
+ paired-threads: 5 # Cores with two paired hyper threads
+ #paired-threads-id: [[0,1],[2,3],[4,5],[6,7],[8,9]] # Guess pinning. By default follows incremental order
+ #threads: 10 # threads awereness of the hyperthreading
+ ##threads-id: [0,1,2,3,4,5,6,7,8,9] #Optional. Guess pinning
+ #cores: 5 # Complete cores, without hyperthreading. VIM ensures the other paired thread is idle
+ ##cores-id: [0,1,2,3,4] # Optional. Guess pinning of cores
+
+ #Optional: Dataplane needed interfaces
+ interfaces:
+ - name: xe0 # Optional. User friendly name
+ vpci: "0000:00:10.0" # Optional. Guess PCI
+ bandwidth: 10 Gbps # Needed minimun bandwidth
+ dedicated: "yes" # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag)
+ - name: xe1
+ vpci: "0000:00:11.0"
+ bandwidth: 10 Gbps
+ dedicated: "no"
+
+ #Optional: List of extra devices
+ devices: # order determines device letter asignation (hda, hdb, ...)
+ - type: disk # "disk","cdrom","xml","usb"
+ imageRef: 37598e34-ccb3-11e4-a996-52540030594e # UUID of an image, only for disk,cdrom,xml
+ # vpci: "0000:00:03.0" # Optional, not for disk or cdrom
+ # xml: 'Only for type xml: a XML described device xml text. Do not use single quotes inside
+ # The following words, if found, will be replaced:
+ # __file__ by image path, (imageiRef must be provided)
+ # __format__ by qcow2 or raw (imageRef must be provided)
+ # __dev__ by device letter (b, c, d ...)
+ # __vpci__ by vpci (vpci must be provided)
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+image:
+ name: image-name
+ description: image-description
+ path: /image-path/at/the/compute-node/storage/image
+ metadata: # Optional extra metadata of the image. All fields are optional
+ use_incremental: "yes" # "yes" by default, "no" Deployed using a incremental qcow2 image
+ vpci: "0000:10:00.0" #requiered PCI at guess
+ os_distro: win # operating system distribution
+ os_type: windows # operating system type "linux" by default, "windows"
+ os_version: "7" # operating system version
+ bus: "ide" # By default "virtio" for linux, "ide" for windows
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+network:
+ name: network-name
+ type: data # "bridge_data" or "bridge_man" for control plane, "data" for dataplane, or "ptp" for point to point dataplane
+ provider:physical: null # needed a value for a bridge_data, bridge_man,
+ # can be:
+ # bridge:iface_name : attached to a iface_name host bridge interface
+ # macvtap:iface_name : attached to a iface_name host physical interface
+ # default : attached to the default host interface
+ # null : for data or ptp types. (To be changed in future versions)
+ shared: true # true, false: if shared it will consider by OPENVIM an EXTERNAL network available at OPENMANO
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+port:
+ name: port-name
+ #network_id: network uuid this port must be attached
+ type: external
+ binding:vlan: 103 # if provided packets to/from this switch port will be vlan tagged
+ binding:switch_port": "Te/01" # provide the openflow switch port
+ #mac_address: 34:44:45:67:78:12 # mac address of the element behind this port. As normally is not know, leave commented
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+server:
+ name: vm-name # name
+ description: vm-description # Optional user description
+ imageRef: 24640fe0-8a9e-11e4-a236-52540056c317 # valid image uuid
+ flavorRef: e22dd8a8-9ca5-11e4-99b6-52540056c317 # valid flavor uuid
+ networks: # List of control plane interfaces, Optional
+ - name: mgmt0 #friendly user name
+ vpci: "0000:00:0a.0" #Optional guess PCI
+ uuid: c09b2f1a-8a9e-11e4-a236-52540056c317 # valid network uuid
+ #mac_address: #guess concrete mac address, by default one is asigned
+ #model: "virtio","e1000","ne2k_pci","pcnet","rtl8139", By default auto, normally virtio
+ start: "yes" # "yes","no","paused". By default it is started upon creted
+ hostId: ec656bc4-9ca5-11e4-99b6-52540056c317 #prefered host where to allocate
+
+ # allocating EXCLUSIVE resoureces in the same NUMA node.
+ # If provided, it overrides extended values at flavor
+ extended: # optional
+ processor_ranking: 100 # minimal processor family. Not used in current version
+ numas: # list of numa set. Only one supported in current version
+ - memory: 8 # GByte of huge pages at this numa
+
+ #Choose among one of "cores", "paired-threads", "threads"
+ paired-threads: 5 # Cores with two paired hyper threads
+ #paired-threads-id: [[0,1],[2,3],[4,5],[6,7],[8,9]] # Guess pinning. By default follows incremental order
+ #threads: 10 # threads awereness of the hyperthreading
+ ##threads-id: [0,1,2,3,4,5,6,7,8,9] #Optional. Guess pinning
+ #cores: 5 # Complete cores, without hyperthreading. VIM ensures the other paired thread is idle
+ ##cores-id: [0,1,2,3,4] # Optional. Guess pinning of cores
+
+ #Optional: Dataplane needed interfaces
+ interfaces:
+ - name: xe0 # Optional. User friendly name
+ vpci: "0000:00:10.0" # Optional. Guess PCI
+ bandwidth: 10 Gbps # Needed minimun bandwidth
+ dedicated: "yes" # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag)
+ # you can attach this inteface to a network at server creation or later with a port attach
+ #uuid: 41bcac58-9be9-11e4-b1b6-52540056c317 # Attach the interface to this network uuid
+ - name: xe1
+ vpci: "0000:00:11.0"
+ bandwidth: 10 Gbps
+ dedicated: "no"
+ #mac_address: #guess concrete mac address, by default one is asigned. Not possible for dedicated: "yes"
+
+ #Optional: List of extra devices
+ devices: # order determines device letter asignation (hda, hdb, ...)
+ - type: disk # "disk","cdrom","xml","usb"
+ imageRef: 37598e34-ccb3-11e4-a996-52540030594e # UUID of an image, only for disk,cdrom,xml
+ # vpci: "0000:00:03.0" # Optional, not for disk or cdrom
+ # xml: 'Only for type xml: a XML described device xml text. Do not use single quotes inside
+ # The following words, if found, will be replaced:
+ # __file__ by image path, (imageiRef must be provided)
+ # __format__ by qcow2 or raw (imageRef must be provided)
+ # __dev__ by device letter (b, c, d ...)
+ # __vpci__ by vpci (vpci must be provided)
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+tenant:
+ name: tenant-name
+ description: tenant-description
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script to test openvim with the creation of flavors and interfaces
+#using images already inserted
+#
+
+echo " Creates 1 flavor, 3 nets, 2 VMs (US)
+ Interfaces: 2 sriov, 1 passthrough, 1 sriov dedicated
+ Test mac address allocation, 1 VM with direct network attach,
+ another VM with network attach after creation"
+echo
+echo -n "type network model to test network model (empty to skip)? "
+read model
+echo "Press enter to continue"
+read kk
+if [ -n "$model" ]
+then
+ model="model: '${model}'"
+fi
+#echo _${model}_
+
+#image to load
+imagePath=/mnt/powervault/virtualization/vnfs/os/US1404.qcow2
+#image to load as an extra disk, can be any
+imagePath_extra=/mnt/powervault/virtualization/vnfs/os/US1404user.qcow2
+#default network to use
+network_eth0=default
+
+
+DIRNAME=`dirname $0`
+
+function del_rubbish(){
+ echo "Press enter to delete the deployed things"
+ read kk
+ [ -n "$DEL_server" ] && ${DIRNAME}/test_openvim.py -f del server $DEL_server
+ [ -n "$DEL_network" ] && ${DIRNAME}/test_openvim.py -f del network $DEL_network
+ [ -n "$DEL_flavor" ] && ${DIRNAME}/test_openvim.py -f del flavor $DEL_flavor
+ [ -n "$DEL_image" ] && ${DIRNAME}/test_openvim.py -f del image $DEL_image
+ rm -f kk.out
+}
+
+function proccess_out(){ # action_text field to retrieve
+ if egrep -q "\"error\"" kk.out
+ then
+ echo "failed to" $1
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+ if [ -z "$2" ] ; then pattern='"id"' ; else pattern="$2" ; fi
+ value=`egrep "$pattern" kk.out `
+ value=${value##* \"}
+ value=${value%\"*}
+ if [[ -z "$value" ]]
+ then
+ echo "not found the field" $2
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+}
+
+#proccess_out "insert server tidgen1" '^ "id"'
+#echo $value
+#exit 0
+
+
+
+echo -n "get ${imagePath##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath" images > kk.out
+proccess_out "get ${imagePath##*/}"
+echo $value
+image1=$value
+
+
+echo -n "get ${imagePath_extra##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath_extra" images > kk.out
+proccess_out "get ${imagePath_extra##*/}"
+echo $value
+image2=$value
+
+
+echo -n "get ${network_eth0} network: "
+${DIRNAME}/test_openvim.py -F"name=$network_eth0" network > kk.out
+proccess_out "get ${network_eth0} network"
+echo $value
+network_eth0=$value
+
+
+echo -n "insert flavor: "
+${DIRNAME}/test_openvim.py new flavor '
+---
+flavor:
+ name: 5PTh_8G_2I
+ description: flavor to test openvim
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ paired-threads: 5
+ interfaces:
+ - name: xe0
+ dedicated: "yes"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:10.0"
+ #mac_address: "10:10:10:10:10:10"
+ - name: xe1
+ dedicated: "yes:sriov"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:11.0"
+ mac_address: "10:10:10:10:10:11"
+' > kk.out
+proccess_out "insert flavor"
+echo $value
+flavor1=$value
+DEL_flavor="$DEL_flavor $flavor1"
+
+
+echo -n "insert ptp net1: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-xe0
+ type: ptp
+' > kk.out
+proccess_out "insert network 0"
+echo $value
+network0=$value
+DEL_network="$DEL_network $value"
+
+
+echo -n "insert ptp net2: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-xe1
+ type: ptp
+' > kk.out
+proccess_out "insert network 1"
+echo $value
+network1=$value
+DEL_network="$DEL_network $value"
+
+
+
+echo -n "insert bridge network net2: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-net2
+ type: bridge_data
+' > kk.out
+proccess_out "insert network 2"
+echo $value
+network2=$value
+DEL_network="$DEL_network $value"
+
+echo -n "insert test VM 1: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM1
+ descrition: US or tidgen with 1 SRIOV 1 PASSTHROUGH
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:10:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:10:13'
+ ${model}
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server1=$value
+DEL_server="$DEL_server $value"
+
+
+
+echo -n "insert test VM 2: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM2
+ descrition: US or tidgen with direct network attach
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ threads: 10
+ interfaces:
+ - name: xe0
+ dedicated: 'yes'
+ bandwidth: '10 Gbps'
+ vpci: '0000:00:10.0'
+ #mac_address: '10:10:10:10:aa:10'
+ uuid: '$network0'
+ - name: xe1
+ dedicated: 'no'
+ bandwidth: '7 Gbps'
+ vpci: '0000:00:11.0'
+ mac_address: '10:10:10:10:aa:11'
+ uuid: '$network1'
+ devices:
+ - type: disk
+ imageRef: '$image2'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:aa:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:aa:13'
+ ${model}
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server2=$value
+DEL_server="$DEL_server $value"
+
+echo -n "get xe0 iface uuid from tidgen1: "
+${DIRNAME}/test_openvim.py -F "device_id=${server1}&name=xe0" ports > kk.out
+proccess_out "get xe0 uuid port from tidgen1"
+echo $value
+iface_xe0=$value
+
+
+echo -n "get xe1 iface uuid from tidgen1: "
+${DIRNAME}/test_openvim.py -F "device_id=${server1}&name=xe1" ports > kk.out
+proccess_out "get xe1 uuid port from tidgen1"
+echo $value
+iface_xe1=$value
+
+
+echo -n "attach xe0 from tidgen1 to network "
+${DIRNAME}/test_openvim.py -f edit ports $iface_xe0 "network_id: $network0" > kk.out
+proccess_out "attach xe0 from tidgen1 to network"
+echo "ok"
+
+
+echo -n "attach xe1 from tidgen1 to network "
+${DIRNAME}/test_openvim.py -f edit ports $iface_xe1 "network_id: $network1" > kk.out
+proccess_out "attach xe1 from tidgen1 to network"
+echo "ok"
+
+
+echo
+echo finish. Check connections!!
+echo click return to delete all deployed things
+echo
+
+del_rubbish
+exit 0
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script to test openvim with the creation of flavors and interfaces
+#using images already inserted
+#
+
+echo " Creates 1 flavor, 3 nets, 2 VMs (US)
+ Interfaces: 2 sriov, 1 passthrough, 1 sriov dedicated
+ Test mac address allocation, 1 VM with direct network attach,
+ another VM with network attach after creation"
+echo
+echo "Press enter to continue"
+read kk
+
+#image to load
+imagePath=/mnt/powervault/virtualization/vnfs/os/US1404.qcow2
+#image to load as an extra disk, can be any
+imagePath_extra=/mnt/powervault/virtualization/vnfs/os/US1404user.qcow2
+#default network to use
+network_eth0=default
+
+
+DIRNAME=`dirname $0`
+
+function del_rubbish(){
+ echo "Press enter to delete the deployed things"
+ read kk
+ [ -n "$DEL_server" ] && ${DIRNAME}/test_openvim.py -f del server $DEL_server
+ [ -n "$DEL_network" ] && ${DIRNAME}/test_openvim.py -f del network $DEL_network
+ [ -n "$DEL_flavor" ] && ${DIRNAME}/test_openvim.py -f del flavor $DEL_flavor
+ [ -n "$DEL_image" ] && ${DIRNAME}/test_openvim.py -f del image $DEL_image
+ rm -f kk.out
+}
+
+function proccess_out(){ # action_text field to retrieve
+ if egrep -q "\"error\"" kk.out
+ then
+ echo "failed to" $1
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+ if [ -z "$2" ] ; then pattern='"id"' ; else pattern="$2" ; fi
+ value=`egrep "$pattern" kk.out `
+ value=${value##* \"}
+ value=${value%\"*}
+ if [[ -z "$value" ]]
+ then
+ echo "not found the field" $2
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+}
+
+#proccess_out "insert server tidgen1" '^ "id"'
+#echo $value
+#exit 0
+
+
+
+echo -n "get ${imagePath##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath" images > kk.out
+proccess_out "get ${imagePath##*/}"
+echo $value
+image1=$value
+
+
+echo -n "get ${imagePath_extra##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath_extra" images > kk.out
+proccess_out "get ${imagePath_extra##*/}"
+echo $value
+image2=$value
+
+
+echo -n "get ${network_eth0} network: "
+${DIRNAME}/test_openvim.py -F"name=$network_eth0" network > kk.out
+proccess_out "get ${network_eth0} network"
+echo $value
+network_eth0=$value
+
+
+echo -n "insert flavor: "
+${DIRNAME}/test_openvim.py new flavor '
+---
+flavor:
+ name: 5PTh_8G_2I
+ description: flavor to test openvim
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ paired-threads: 5
+ interfaces:
+ - name: xe0
+ dedicated: "yes"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:10.0"
+ #mac_address: "10:10:10:10:10:10"
+ - name: xe1
+ dedicated: "no"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:11.0"
+ mac_address: "10:10:10:10:10:11"
+' > kk.out
+proccess_out "insert flavor"
+echo $value
+flavor1=$value
+DEL_flavor="$DEL_flavor $flavor1"
+
+
+echo -n "insert ptp net1: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-xe0
+ type: ptp
+' > kk.out
+proccess_out "insert network 0"
+echo $value
+network0=$value
+DEL_network="$DEL_network $value"
+
+
+echo -n "insert ptp net2: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-xe1
+ type: ptp
+' > kk.out
+proccess_out "insert network 1"
+echo $value
+network1=$value
+DEL_network="$DEL_network $value"
+
+
+
+echo -n "insert bridge network net2: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-net2
+ type: bridge_data
+' > kk.out
+proccess_out "insert network 2"
+echo $value
+network2=$value
+DEL_network="$DEL_network $value"
+
+echo -n "insert test VM 1: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM1
+ descrition: US or tidgen with 1 SRIOV 1 PASSTHROUGH
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:10:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:10:13'
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server1=$value
+DEL_server="$DEL_server $value"
+
+
+
+echo -n "insert test VM 2: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM2
+ descrition: US or tidgen with direct network attach
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:aa:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:aa:13'
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ threads: 10
+ interfaces:
+ - name: xe0
+ dedicated: 'yes'
+ bandwidth: '10 Gbps'
+ vpci: '0000:00:10.0'
+ #mac_address: '10:10:10:10:aa:10'
+ uuid: '$network0'
+ - name: xe1
+ dedicated: 'no'
+ bandwidth: '7 Gbps'
+ vpci: '0000:00:11.0'
+ mac_address: '10:10:10:10:aa:11'
+ uuid: '$network1'
+ devices:
+ - type: disk
+ imageRef: '$image2'
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server2=$value
+DEL_server="$DEL_server $value"
+
+echo -n "get xe0 iface uuid from tidgen1: "
+${DIRNAME}/test_openvim.py -F "device_id=${server1}&name=xe0" ports > kk.out
+proccess_out "get xe0 uuid port from tidgen1"
+echo $value
+iface_xe0=$value
+
+
+echo -n "get xe1 iface uuid from tidgen1: "
+${DIRNAME}/test_openvim.py -F "device_id=${server1}&name=xe1" ports > kk.out
+proccess_out "get xe1 uuid port from tidgen1"
+echo $value
+iface_xe1=$value
+
+
+echo -n "attach xe0 from tidgen1 to network "
+${DIRNAME}/test_openvim.py -f edit ports $iface_xe0 "network_id: $network0" > kk.out
+proccess_out "attach xe0 from tidgen1 to network"
+echo "ok"
+
+
+echo -n "attach xe1 from tidgen1 to network "
+${DIRNAME}/test_openvim.py -f edit ports $iface_xe1 "network_id: $network1" > kk.out
+proccess_out "attach xe1 from tidgen1 to network"
+echo "ok"
+
+
+echo
+echo finsish. Check connections!!
+echo
+
+del_rubbish
+exit 0
+
--- /dev/null
+#!/bin/bash
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script to test openvim with the creation of flavors and interfaces
+#in cloud style
+#
+
+echo " Creates 1 flavor, 3 nets, 2 VMs (US)
+ WITHOUT huge pages, nor NUMA assigment
+ network attach after creation"
+echo
+echo "Press enter to continue"
+read kk
+
+#image to load
+imagePath=/mnt/powervault/virtualization/vnfs/os/US1404.qcow2
+#image to load as an extra disk, can be any
+imagePath_extra=/mnt/powervault/virtualization/vnfs/os/US1404user.qcow2
+#default network to use
+network_eth0=default
+
+
+DIRNAME=`dirname $0`
+
+function del_rubbish(){
+ echo "Press enter to delete the deployed things"
+ read kk
+ [ -n "$DEL_server" ] && ${DIRNAME}/test_openvim.py -f del server $DEL_server
+ [ -n "$DEL_network" ] && ${DIRNAME}/test_openvim.py -f del network $DEL_network
+ [ -n "$DEL_flavor" ] && ${DIRNAME}/test_openvim.py -f del flavor $DEL_flavor
+ [ -n "$DEL_image" ] && ${DIRNAME}/test_openvim.py -f del image $DEL_image
+ rm -f kk.out
+}
+
+function proccess_out(){ # action_text field to retrieve
+ if egrep -q "\"error\"" kk.out
+ then
+ echo "failed to" $1
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+ if [ -z "$2" ] ; then pattern='"id"' ; else pattern="$2" ; fi
+ value=`egrep "$pattern" kk.out `
+ value=${value##* \"}
+ value=${value%\"*}
+ if [[ -z "$value" ]]
+ then
+ echo "not found the field" $2
+ cat kk.out
+ del_rubbish
+ exit -1
+ fi
+}
+
+#proccess_out "insert server tidgen1" '^ "id"'
+#echo $value
+#exit 0
+
+
+
+echo -n "get ${imagePath##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath" images > kk.out
+proccess_out "get ${imagePath##*/}"
+echo $value
+image1=$value
+
+
+echo -n "get ${imagePath_extra##*/} image: "
+${DIRNAME}/test_openvim.py -F"path=$imagePath_extra" images > kk.out
+proccess_out "get ${imagePath_extra##*/}"
+echo $value
+image2=$value
+
+
+echo -n "get ${network_eth0} network: "
+${DIRNAME}/test_openvim.py -F"name=$network_eth0" network > kk.out
+proccess_out "get ${network_eth0} network"
+echo $value
+network_eth0=$value
+
+
+echo -n "insert flavor: "
+${DIRNAME}/test_openvim.py new flavor '
+---
+flavor:
+ name: CloudVM
+ description: normal cloud image with 1G, 1core
+ ram: 1024
+ vcpus: 1
+' > kk.out
+proccess_out "insert flavor"
+echo $value
+flavor1=$value
+DEL_flavor="$DEL_flavor $flavor1"
+
+
+echo
+echo "Press enter to continue"
+read kk
+
+echo -n "insert bridge network net2: "
+${DIRNAME}/test_openvim.py new network '
+---
+network:
+ name: network-bridge
+ type: bridge_data
+' > kk.out
+proccess_out "insert network 2"
+echo $value
+network2=$value
+DEL_network="$DEL_network $value"
+
+echo -n "insert test VM 1: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM1
+ descrition: US 1 core
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:10:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:10:13'
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server1=$value
+DEL_server="$DEL_server $value"
+
+
+echo
+echo "Press enter to continue"
+read kk
+
+echo -n "insert test VM 2: "
+${DIRNAME}/test_openvim.py new server "
+---
+server:
+ name: test_VM2
+ descrition: US 1G 1core
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ ram: 1024
+ vcpus: 1
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:aa:12'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:aa:13'
+ extended:
+ devices:
+ - type: disk
+ imageRef: '$image2'
+" > kk.out
+proccess_out "insert test VM 2" '^ "id"'
+echo $value
+server2=$value
+DEL_server="$DEL_server $value"
+
+echo
+echo finsish. Check connections!!
+echo
+
+del_rubbish
+exit 0
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+{
+"flavor":{
+ "name":"2Th_2G",
+ "description":"2 Threads, 2Gbyte, only 2 management interfaces",
+ "extended":{
+ "processor_ranking":205,
+ "numas":[
+ {
+ "memory":2,
+ "threads":2
+ }
+ ]
+ }
+}
+}
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+# 5Th_8G_2P
+flavor:
+ name: 5PTh_8G_2I
+ description: 5Pair threads (10tthreads) 8GB memory and 2 dataplane passthrough interfaces
+ extended:
+ processor_ranking: 100
+ numas:
+ - memory: 8
+ paired-threads: 5
+ interfaces:
+ - name: xe0
+ vpci: "0000:00:10.0"
+ bandwidth: 10 Gbps
+ dedicated: "yes"
+ - name: xe1
+ vpci: "0000:00:11.0"
+ bandwidth: 10 Gbps
+ dedicated: "yes"
--- /dev/null
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+{
+ "${}":[
+ "${name} flavor name",
+ "${description} flavor description (${name})",
+ "${processor_ranking} processnor ranking (100)",
+ "${memory} memory in GB (2)",
+ "${threads} threads needed (2)"
+ ],
+
+"flavor":{
+ "name":"${name}",
+ "description":"${description}",
+ "extended":{
+ "processor_ranking":"${processor_ranking int}",
+ "numas":[
+ {
+ "memory":"${memory int}",
+ "threads":"${threads int}"
+ }
+ ]
+ }
+}
+}
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script that obtain the parameters from the configuration file
+#it is a bit unsafe because a comment in the yaml configuration file
+#can wrong this script
+
+#get params from configuration file
+
+[ -z "$1" ] && echo "usage: $0 [openvim_cfg_file]" && exit
+
+OPENVIM_PORT=`grep http_port: $1`
+OPENVIM_PORT=${OPENVIM_PORT#http_port:}
+OPENVIM_PORT=${OPENVIM_PORT%%#*}
+OPENVIM_PORT=`eval echo ${OPENVIM_PORT}` # remove white spaces
+
+OPENVIM_ADMIN_PORT=`grep http_admin_port: $1`
+OPENVIM_ADMIN_PORT=${OPENVIM_ADMIN_PORT#http_admin_port:}
+OPENVIM_ADMIN_PORT=${OPENVIM_ADMIN_PORT%%#*}
+OPENVIM_ADMIN_PORT=`eval echo ${OPENVIM_ADMIN_PORT}` # remove white spaces
+
+OPENVIM_HOST=`grep http_host: $1`
+OPENVIM_HOST=${OPENVIM_HOST#http_host:}
+OPENVIM_HOST=${OPENVIM_HOST%%#*}
+OPENVIM_HOST=`eval echo ${OPENVIM_HOST}` # remove white spaces
+
+OPENVIM_OF_IP=`grep of_controller_ip: $1`
+OPENVIM_OF_IP=${OPENVIM_OF_IP#of_controller_ip:}
+OPENVIM_OF_IP=${OPENVIM_OF_IP%%#*}
+OPENVIM_OF_IP=`eval echo ${OPENVIM_OF_IP}` # remove white spaces
+
+OPENVIM_OF_PORT=`grep of_controller_port: $1`
+OPENVIM_OF_PORT=${OPENVIM_OF_PORT#of_controller_port:}
+OPENVIM_OF_PORT=${OPENVIM_OF_PORT%%#*}
+OPENVIM_OF_PORT=`eval echo ${OPENVIM_OF_PORT}` # remove white spaces
+
+OPENVIM_OF_DPID=`grep of_controller_dpid: $1`
+OPENVIM_OF_DPID=${OPENVIM_OF_DPID#of_controller_dpid:}
+OPENVIM_OF_DPID=${OPENVIM_OF_DPID%%#*}
+OPENVIM_OF_DPID=`eval echo ${OPENVIM_OF_DPID}` # remove white spaces
+
--- /dev/null
+{
+ "host":{
+ "name": "fake-host-0",
+ "user": "user",
+ "password": "password",
+ "ip_name": "fakehost0"
+ },
+"host-data":
+{
+ "name": "fake-host-0",
+ "ranking": 300,
+ "description": "fake host 0 for test mode",
+ "ip_name": "fakehost0",
+ "features": "lps,dioc,hwsv,ht,64b,tlbps",
+ "user": "user",
+ "password": "password",
+ "numas": [
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 12,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 36,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 13
+ },
+ {
+ "core_id": 1,
+ "thread_id": 37
+ },
+ {
+ "core_id": 2,
+ "thread_id": 14
+ },
+ {
+ "core_id": 2,
+ "thread_id": 38
+ },
+ {
+ "core_id": 3,
+ "thread_id": 15
+ },
+ {
+ "core_id": 3,
+ "thread_id": 39
+ },
+ {
+ "core_id": 4,
+ "thread_id": 16
+ },
+ {
+ "core_id": 4,
+ "thread_id": 40
+ },
+ {
+ "core_id": 5,
+ "thread_id": 17
+ },
+ {
+ "core_id": 5,
+ "thread_id": 41
+ },
+ {
+ "core_id": 6,
+ "thread_id": 18
+ },
+ {
+ "core_id": 6,
+ "thread_id": 42
+ },
+ {
+ "core_id": 7,
+ "thread_id": 19
+ },
+ {
+ "core_id": 7,
+ "thread_id": 43
+ },
+ {
+ "core_id": 8,
+ "thread_id": 20
+ },
+ {
+ "core_id": 8,
+ "thread_id": 44
+ },
+ {
+ "core_id": 9,
+ "thread_id": 21
+ },
+ {
+ "core_id": 9,
+ "thread_id": 45
+ },
+ {
+ "core_id": 10,
+ "thread_id": 22
+ },
+ {
+ "core_id": 10,
+ "thread_id": 46
+ },
+ {
+ "core_id": 11,
+ "thread_id": 23
+ },
+ {
+ "core_id": 11,
+ "thread_id": 47
+ }
+ ],
+ "numa_socket": 1,
+ "hugepages": 28,
+ "memory": 32
+ },
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 0,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 24,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 1
+ },
+ {
+ "core_id": 1,
+ "thread_id": 25
+ },
+ {
+ "core_id": 2,
+ "thread_id": 2
+ },
+ {
+ "core_id": 2,
+ "thread_id": 26
+ },
+ {
+ "core_id": 3,
+ "thread_id": 3
+ },
+ {
+ "core_id": 3,
+ "thread_id": 27
+ },
+ {
+ "core_id": 4,
+ "thread_id": 4
+ },
+ {
+ "core_id": 4,
+ "thread_id": 28
+ },
+ {
+ "core_id": 5,
+ "thread_id": 5
+ },
+ {
+ "core_id": 5,
+ "thread_id": 29
+ },
+ {
+ "core_id": 6,
+ "thread_id": 6
+ },
+ {
+ "core_id": 6,
+ "thread_id": 30
+ },
+ {
+ "core_id": 7,
+ "thread_id": 7
+ },
+ {
+ "core_id": 7,
+ "thread_id": 31
+ },
+ {
+ "core_id": 8,
+ "thread_id": 8
+ },
+ {
+ "core_id": 8,
+ "thread_id": 32
+ },
+ {
+ "core_id": 9,
+ "thread_id": 9
+ },
+ {
+ "core_id": 9,
+ "thread_id": 33
+ },
+ {
+ "core_id": 10,
+ "thread_id": 10
+ },
+ {
+ "core_id": 10,
+ "thread_id": 34
+ },
+ {
+ "core_id": 11,
+ "thread_id": 11
+ },
+ {
+ "core_id": 11,
+ "thread_id": 35
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "eth8",
+ "Mbps": 10000,
+ "pci": "0000:08:00.0",
+ "switch_port": "port0/2",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "a6:12:27:bd:2d:ef",
+ "pci": "0000:08:10.0",
+ "source_name": 0,
+ "vlan": 106
+ },
+ {
+ "mac": "be:8a:40:58:cf:de",
+ "pci": "0000:08:10.2",
+ "source_name": 1,
+ "vlan": 107
+ },
+ {
+ "mac": "c6:bf:7a:30:13:55",
+ "pci": "0000:08:10.4",
+ "source_name": 2,
+ "vlan": 103
+ },
+ {
+ "mac": "be:32:50:ef:ea:4e",
+ "pci": "0000:08:10.6",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "ae:36:60:bc:41:78",
+ "pci": "0000:08:11.0",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "52:22:3a:99:67:4e",
+ "pci": "0000:08:11.2",
+ "source_name": 5,
+ "vlan": 104
+ },
+ {
+ "mac": "0a:b8:00:2c:8a:b2",
+ "pci": "0000:08:11.4",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "e2:f6:70:83:a3:ec",
+ "pci": "0000:08:11.6",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "90:e2:ba:0c:36:4c"
+ },
+ {
+ "source_name": "eth17",
+ "Mbps": 10000,
+ "pci": "0000:08:00.1",
+ "switch_port": "port0/3",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "92:0e:27:0c:ad:eb",
+ "pci": "0000:08:10.1",
+ "source_name": 0,
+ "vlan": 105
+ },
+ {
+ "mac": "be:99:65:e8:98:a3",
+ "pci": "0000:08:10.3",
+ "source_name": 1,
+ "vlan": 106
+ },
+ {
+ "mac": "8a:3b:54:42:88:b2",
+ "pci": "0000:08:10.5",
+ "source_name": 2,
+ "vlan": 101
+ },
+ {
+ "mac": "c6:5d:8a:c5:05:f7",
+ "pci": "0000:08:10.7",
+ "source_name": 3,
+ "vlan": 103
+ },
+ {
+ "mac": "96:bd:61:02:4f:d6",
+ "pci": "0000:08:11.1",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "22:5d:85:2c:1b:fd",
+ "pci": "0000:08:11.3",
+ "source_name": 5,
+ "vlan": 104
+ },
+ {
+ "mac": "e6:7f:8a:48:bc:26",
+ "pci": "0000:08:11.5",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "2e:4a:e6:68:18:fa",
+ "pci": "0000:08:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "90:e2:ba:0c:36:4d"
+ },
+ {
+ "source_name": "eth26",
+ "Mbps": 10000,
+ "pci": "0000:06:00.0",
+ "switch_port": "port0/0",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "02:79:15:02:ad:cc",
+ "pci": "0000:06:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "06:81:ef:de:ec:6b",
+ "pci": "0000:06:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "f2:4e:96:f3:8e:73",
+ "pci": "0000:06:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "e2:86:91:23:c5:76",
+ "pci": "0000:06:10.6",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "52:12:a0:77:cc:47",
+ "pci": "0000:06:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "ca:17:4a:c4:cb:bf",
+ "pci": "0000:06:11.2",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "de:13:4c:5d:70:e8",
+ "pci": "0000:06:11.4",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "0a:5f:d2:db:7f:e2",
+ "pci": "0000:06:11.6",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "90:e2:ba:0b:6b:b4"
+ },
+ {
+ "source_name": "eth35",
+ "Mbps": 10000,
+ "pci": "0000:06:00.1",
+ "switch_port": "port0/1",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "a2:08:55:fe:c5:db",
+ "pci": "0000:06:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "7a:dd:c7:46:2a:91",
+ "pci": "0000:06:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "92:a8:1d:4e:cc:a8",
+ "pci": "0000:06:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "02:1a:d6:87:c4:cc",
+ "pci": "0000:06:10.7",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "ea:1b:8b:2a:da:9a",
+ "pci": "0000:06:11.1",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "aa:c3:fe:e5:f2:96",
+ "pci": "0000:06:11.3",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "e2:66:1f:00:b3:45",
+ "pci": "0000:06:11.5",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "da:3c:d8:a3:f4:e0",
+ "pci": "0000:06:11.7",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "90:e2:ba:0b:6b:b5"
+ }
+ ],
+ "numa_socket": 0,
+ "hugepages": 28,
+ "memory": 32
+ }
+ ]
+}
+}
+
--- /dev/null
+{
+"host":{
+ "name": "fake-host-1",
+ "user": "user",
+ "password": "password",
+ "ip_name": "fakehost1"
+},
+"host-data":
+{
+ "name": "fake-host-1",
+ "ranking": 300,
+ "description": "fake host 1 for test mode",
+ "ip_name": "fakehost1",
+ "features": "lps,dioc,hwsv,ht,64b,tlbps",
+ "user": "user",
+ "password": "password",
+ "numas": [
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 1,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 25,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 3
+ },
+ {
+ "core_id": 1,
+ "thread_id": 27
+ },
+ {
+ "core_id": 2,
+ "thread_id": 5
+ },
+ {
+ "core_id": 2,
+ "thread_id": 29
+ },
+ {
+ "core_id": 3,
+ "thread_id": 7
+ },
+ {
+ "core_id": 3,
+ "thread_id": 31
+ },
+ {
+ "core_id": 4,
+ "thread_id": 9
+ },
+ {
+ "core_id": 4,
+ "thread_id": 33
+ },
+ {
+ "core_id": 5,
+ "thread_id": 11
+ },
+ {
+ "core_id": 5,
+ "thread_id": 35
+ },
+ {
+ "core_id": 6,
+ "thread_id": 13
+ },
+ {
+ "core_id": 6,
+ "thread_id": 37
+ },
+ {
+ "core_id": 7,
+ "thread_id": 15
+ },
+ {
+ "core_id": 7,
+ "thread_id": 39
+ },
+ {
+ "core_id": 8,
+ "thread_id": 17
+ },
+ {
+ "core_id": 8,
+ "thread_id": 41
+ },
+ {
+ "core_id": 9,
+ "thread_id": 19
+ },
+ {
+ "core_id": 9,
+ "thread_id": 43
+ },
+ {
+ "core_id": 10,
+ "thread_id": 21
+ },
+ {
+ "core_id": 10,
+ "thread_id": 45
+ },
+ {
+ "core_id": 11,
+ "thread_id": 23
+ },
+ {
+ "core_id": 11,
+ "thread_id": 47
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p2p1",
+ "Mbps": 10000,
+ "pci": "0000:44:00.0",
+ "switch_port": "port0/4",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "4e:2e:a5:ab:7f:4b",
+ "pci": "0000:44:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "a6:43:f6:4f:b6:ea",
+ "pci": "0000:44:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "b2:fe:29:81:71:0a",
+ "pci": "0000:44:10.4",
+ "source_name": 2,
+ "vlan": 104
+ },
+ {
+ "mac": "aa:9d:13:62:80:e5",
+ "pci": "0000:44:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "3a:30:7f:c6:67:04",
+ "pci": "0000:44:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "c6:20:45:8f:30:08",
+ "pci": "0000:44:11.2",
+ "source_name": 5,
+ "vlan": 100
+ },
+ {
+ "mac": "66:3d:09:bc:d6:32",
+ "pci": "0000:44:11.4",
+ "source_name": 6,
+ "vlan": 105
+ },
+ {
+ "mac": "46:e5:0c:f6:c4:ae",
+ "pci": "0000:44:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:14"
+ },
+ {
+ "source_name": "p2p2",
+ "Mbps": 10000,
+ "pci": "0000:44:00.1",
+ "switch_port": "port0/5",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "ea:b6:5d:98:d9:86",
+ "pci": "0000:44:10.1",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "36:4c:31:5a:5f:66",
+ "pci": "0000:44:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "42:a0:5d:fa:8d:8c",
+ "pci": "0000:44:10.5",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "c6:50:23:c5:53:ff",
+ "pci": "0000:44:10.7",
+ "source_name": 3,
+ "vlan": 104
+ },
+ {
+ "mac": "12:2e:6c:79:a3:cc",
+ "pci": "0000:44:11.1",
+ "source_name": 4,
+ "vlan": 101
+ },
+ {
+ "mac": "be:f0:8e:7b:50:46",
+ "pci": "0000:44:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "6e:6e:15:44:1c:1d",
+ "pci": "0000:44:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "9a:e8:1e:e6:af:31",
+ "pci": "0000:44:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:16"
+ },
+ {
+ "source_name": "p3p2",
+ "Mbps": 10000,
+ "pci": "0000:43:00.1",
+ "switch_port": "port0/7",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "2e:b7:dc:aa:9a:35",
+ "pci": "0000:43:10.1",
+ "source_name": 0,
+ "vlan": 103
+ },
+ {
+ "mac": "42:71:39:50:92:a5",
+ "pci": "0000:43:10.3",
+ "source_name": 1,
+ "vlan": 100
+ },
+ {
+ "mac": "22:ac:a0:64:a4:00",
+ "pci": "0000:43:10.5",
+ "source_name": 2,
+ "vlan": 101
+ },
+ {
+ "mac": "7e:81:e0:56:c0:aa",
+ "pci": "0000:43:10.7",
+ "source_name": 3,
+ "vlan": 102
+ },
+ {
+ "mac": "de:87:8d:ed:81:1a",
+ "pci": "0000:43:11.1",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "fe:29:e9:da:45:df",
+ "pci": "0000:43:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "b6:e7:67:46:22:de",
+ "pci": "0000:43:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "5e:9e:10:7a:66:e9",
+ "pci": "0000:43:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:0a"
+ },
+ {
+ "source_name": "p3p1",
+ "Mbps": 10000,
+ "pci": "0000:43:00.0",
+ "switch_port": "port0/6",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "5e:2e:05:67:7b:05",
+ "pci": "0000:43:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "fa:a4:01:f0:7e:d4",
+ "pci": "0000:43:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "32:aa:7b:9a:e3:6b",
+ "pci": "0000:43:10.4",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "fa:8d:b3:2f:a6:59",
+ "pci": "0000:43:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "f2:92:f3:f0:ba:06",
+ "pci": "0000:43:11.0",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "c2:8a:1b:55:13:52",
+ "pci": "0000:43:11.2",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "5a:55:8e:f4:a5:4f",
+ "pci": "0000:43:11.4",
+ "source_name": 6,
+ "vlan": 107
+ },
+ {
+ "mac": "8a:21:a1:bb:d4:b2",
+ "pci": "0000:43:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:08"
+ }
+ ],
+ "numa_socket": 1,
+ "hugepages": 28,
+ "memory": 32
+ },
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 0,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 24,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 2
+ },
+ {
+ "core_id": 1,
+ "thread_id": 26
+ },
+ {
+ "core_id": 2,
+ "thread_id": 4
+ },
+ {
+ "core_id": 2,
+ "thread_id": 28
+ },
+ {
+ "core_id": 3,
+ "thread_id": 6
+ },
+ {
+ "core_id": 3,
+ "thread_id": 30
+ },
+ {
+ "core_id": 4,
+ "thread_id": 8
+ },
+ {
+ "core_id": 4,
+ "thread_id": 32
+ },
+ {
+ "core_id": 5,
+ "thread_id": 10
+ },
+ {
+ "core_id": 5,
+ "thread_id": 34
+ },
+ {
+ "core_id": 6,
+ "thread_id": 12
+ },
+ {
+ "core_id": 6,
+ "thread_id": 36
+ },
+ {
+ "core_id": 7,
+ "thread_id": 14
+ },
+ {
+ "core_id": 7,
+ "thread_id": 38
+ },
+ {
+ "core_id": 8,
+ "thread_id": 16
+ },
+ {
+ "core_id": 8,
+ "thread_id": 40
+ },
+ {
+ "core_id": 9,
+ "thread_id": 18
+ },
+ {
+ "core_id": 9,
+ "thread_id": 42
+ },
+ {
+ "core_id": 10,
+ "thread_id": 20
+ },
+ {
+ "core_id": 10,
+ "thread_id": 44
+ },
+ {
+ "core_id": 11,
+ "thread_id": 22
+ },
+ {
+ "core_id": 11,
+ "thread_id": 46
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p5p1",
+ "Mbps": 10000,
+ "pci": "0000:04:00.0",
+ "switch_port": "port0/8",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "46:28:47:80:9d:fe",
+ "pci": "0000:04:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "5a:5d:cb:52:93:66",
+ "pci": "0000:04:10.2",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "42:42:2e:2d:44:9d",
+ "pci": "0000:04:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "4a:ef:c2:a5:c8:ad",
+ "pci": "0000:04:10.6",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "0a:0c:b1:8b:da:c6",
+ "pci": "0000:04:11.0",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "46:e7:85:ad:1f:3e",
+ "pci": "0000:04:11.2",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "5e:a4:8f:8f:b0:53",
+ "pci": "0000:04:11.4",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "d2:76:f2:21:fb:42",
+ "pci": "0000:04:11.6",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:33:09:6c"
+ },
+ {
+ "source_name": "p5p2",
+ "Mbps": 10000,
+ "pci": "0000:04:00.1",
+ "switch_port": "port0/9",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "f2:c8:15:59:9d:9e",
+ "pci": "0000:04:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "ca:30:b2:c2:d9:d6",
+ "pci": "0000:04:10.3",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "1a:03:de:f7:f5:db",
+ "pci": "0000:04:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "ca:6d:e0:c3:1e:f5",
+ "pci": "0000:04:10.7",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "aa:35:ab:70:29:5c",
+ "pci": "0000:04:11.1",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "02:b3:c4:a6:12:df",
+ "pci": "0000:04:11.3",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "52:4f:13:67:d5:1f",
+ "pci": "0000:04:11.5",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "3a:7f:b2:58:61:81",
+ "pci": "0000:04:11.7",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:33:09:6e"
+ },
+ {
+ "source_name": "p7p1",
+ "Mbps": 10000,
+ "pci": "0000:06:00.0",
+ "switch_port": "port0/10",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "6e:51:ee:3c:66:fa",
+ "pci": "0000:06:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "26:0c:7a:db:9b:7e",
+ "pci": "0000:06:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "66:b0:59:cf:6b:fc",
+ "pci": "0000:06:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "f6:52:a5:ff:97:b9",
+ "pci": "0000:06:10.6",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "4a:5c:b2:6a:25:15",
+ "pci": "0000:06:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "86:0f:85:c2:42:b1",
+ "pci": "0000:06:11.2",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "b2:3d:24:1d:3e:40",
+ "pci": "0000:06:11.4",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "ca:3f:fc:7b:32:36",
+ "pci": "0000:06:11.6",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:33:0c:d4"
+ },
+ {
+ "source_name": "p7p2",
+ "Mbps": 10000,
+ "pci": "0000:06:00.1",
+ "switch_port": "port0/11",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "76:28:ae:b5:31:25",
+ "pci": "0000:06:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "5e:fa:d1:f7:eb:44",
+ "pci": "0000:06:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "56:d3:6a:b0:af:2e",
+ "pci": "0000:06:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "3e:75:f3:00:aa:ba",
+ "pci": "0000:06:10.7",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "5e:fd:b0:e2:59:47",
+ "pci": "0000:06:11.1",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "ee:a7:2f:ab:73:0f",
+ "pci": "0000:06:11.3",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "d6:e9:f1:c6:40:00",
+ "pci": "0000:06:11.5",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "36:b0:af:0e:5b:68",
+ "pci": "0000:06:11.7",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:33:0c:d6"
+ }
+ ],
+ "numa_socket": 0,
+ "hugepages": 28,
+ "memory": 32
+ }
+ ]
+}
+}
--- /dev/null
+{
+ "host":{
+ "name": "fake-host-2",
+ "user": "user",
+ "password": "password",
+ "ip_name": "fakehost2"
+ },
+"host-data":
+{
+ "name": "fake-host-2",
+ "ranking": 300,
+ "description": "fake host 2 for test mode",
+ "ip_name": "fakehost2",
+ "features": "lps,dioc,hwsv,ht,64b,tlbps",
+ "user": "user",
+ "password": "password",
+ "numas": [
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 1,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 25,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 3
+ },
+ {
+ "core_id": 1,
+ "thread_id": 27
+ },
+ {
+ "core_id": 2,
+ "thread_id": 5
+ },
+ {
+ "core_id": 2,
+ "thread_id": 29
+ },
+ {
+ "core_id": 3,
+ "thread_id": 7
+ },
+ {
+ "core_id": 3,
+ "thread_id": 31
+ },
+ {
+ "core_id": 4,
+ "thread_id": 9
+ },
+ {
+ "core_id": 4,
+ "thread_id": 33
+ },
+ {
+ "core_id": 5,
+ "thread_id": 11
+ },
+ {
+ "core_id": 5,
+ "thread_id": 35
+ },
+ {
+ "core_id": 6,
+ "thread_id": 13
+ },
+ {
+ "core_id": 6,
+ "thread_id": 37
+ },
+ {
+ "core_id": 7,
+ "thread_id": 15
+ },
+ {
+ "core_id": 7,
+ "thread_id": 39
+ },
+ {
+ "core_id": 8,
+ "thread_id": 17
+ },
+ {
+ "core_id": 8,
+ "thread_id": 41
+ },
+ {
+ "core_id": 9,
+ "thread_id": 19
+ },
+ {
+ "core_id": 9,
+ "thread_id": 43
+ },
+ {
+ "core_id": 10,
+ "thread_id": 21
+ },
+ {
+ "core_id": 10,
+ "thread_id": 45
+ },
+ {
+ "core_id": 11,
+ "thread_id": 23
+ },
+ {
+ "core_id": 11,
+ "thread_id": 47
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p2p1",
+ "Mbps": 10000,
+ "pci": "0000:44:00.0",
+ "switch_port": "port0/12",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "fe:49:52:59:2f:0b",
+ "pci": "0000:44:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "16:df:ee:65:d1:a9",
+ "pci": "0000:44:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "e6:10:3a:09:cc:eb",
+ "pci": "0000:44:10.4",
+ "source_name": 2,
+ "vlan": 104
+ },
+ {
+ "mac": "f6:8e:ef:92:70:9a",
+ "pci": "0000:44:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "fa:a5:0e:21:bc:89",
+ "pci": "0000:44:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "fa:72:5a:b0:07:44",
+ "pci": "0000:44:11.2",
+ "source_name": 5,
+ "vlan": 100
+ },
+ {
+ "mac": "3e:8f:5c:1a:5d:e1",
+ "pci": "0000:44:11.4",
+ "source_name": 6,
+ "vlan": 105
+ },
+ {
+ "mac": "f2:e0:34:e2:e1:2d",
+ "pci": "0000:44:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:35:e9:a0"
+ },
+ {
+ "source_name": "p2p2",
+ "Mbps": 10000,
+ "pci": "0000:44:00.1",
+ "switch_port": "port0/13",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "ba:76:24:e0:79:75",
+ "pci": "0000:44:10.1",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "b2:26:81:5b:18:7b",
+ "pci": "0000:44:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "da:46:a9:f4:ab:42",
+ "pci": "0000:44:10.5",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "86:e0:ec:33:cc:18",
+ "pci": "0000:44:10.7",
+ "source_name": 3,
+ "vlan": 104
+ },
+ {
+ "mac": "ee:ee:49:da:20:75",
+ "pci": "0000:44:11.1",
+ "source_name": 4,
+ "vlan": 101
+ },
+ {
+ "mac": "be:fa:18:da:f3:ba",
+ "pci": "0000:44:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "96:fa:8d:ed:50:02",
+ "pci": "0000:44:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "22:1b:cb:33:a7:5a",
+ "pci": "0000:44:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:35:e9:a2"
+ },
+ {
+ "source_name": "p3p2",
+ "Mbps": 10000,
+ "pci": "0000:43:00.1",
+ "switch_port": "port0/15",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "a6:6e:28:6c:5b:04",
+ "pci": "0000:43:10.1",
+ "source_name": 0,
+ "vlan": 103
+ },
+ {
+ "mac": "be:35:42:86:3d:fd",
+ "pci": "0000:43:10.3",
+ "source_name": 1,
+ "vlan": 100
+ },
+ {
+ "mac": "0a:92:99:92:02:e4",
+ "pci": "0000:43:10.5",
+ "source_name": 2,
+ "vlan": 101
+ },
+ {
+ "mac": "b6:a6:3a:f1:1e:57",
+ "pci": "0000:43:10.7",
+ "source_name": 3,
+ "vlan": 102
+ },
+ {
+ "mac": "be:3f:1a:ef:76:c0",
+ "pci": "0000:43:11.1",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "6a:cc:a6:bf:61:cd",
+ "pci": "0000:43:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "fe:c1:15:a9:c5:12",
+ "pci": "0000:43:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "6a:ff:59:aa:63:34",
+ "pci": "0000:43:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:42"
+ },
+ {
+ "source_name": "p3p1",
+ "Mbps": 10000,
+ "pci": "0000:43:00.0",
+ "switch_port": "port0/14",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "c6:18:cb:6c:c4:04",
+ "pci": "0000:43:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "a6:4e:90:89:90:d8",
+ "pci": "0000:43:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "fe:9a:82:91:a5:3a",
+ "pci": "0000:43:10.4",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "22:a3:ef:27:50:fd",
+ "pci": "0000:43:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "c6:57:98:69:67:e2",
+ "pci": "0000:43:11.0",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "6a:f9:81:ae:40:94",
+ "pci": "0000:43:11.2",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "86:00:37:7f:6e:05",
+ "pci": "0000:43:11.4",
+ "source_name": 6,
+ "vlan": 107
+ },
+ {
+ "mac": "f2:e6:fc:7d:71:9b",
+ "pci": "0000:43:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:35:ed:40"
+ }
+ ],
+ "numa_socket": 1,
+ "hugepages": 60,
+ "memory": 64
+ },
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 0,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 24,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 2
+ },
+ {
+ "core_id": 1,
+ "thread_id": 26
+ },
+ {
+ "core_id": 2,
+ "thread_id": 4
+ },
+ {
+ "core_id": 2,
+ "thread_id": 28
+ },
+ {
+ "core_id": 3,
+ "thread_id": 6
+ },
+ {
+ "core_id": 3,
+ "thread_id": 30
+ },
+ {
+ "core_id": 4,
+ "thread_id": 8
+ },
+ {
+ "core_id": 4,
+ "thread_id": 32
+ },
+ {
+ "core_id": 5,
+ "thread_id": 10
+ },
+ {
+ "core_id": 5,
+ "thread_id": 34
+ },
+ {
+ "core_id": 6,
+ "thread_id": 12
+ },
+ {
+ "core_id": 6,
+ "thread_id": 36
+ },
+ {
+ "core_id": 7,
+ "thread_id": 14
+ },
+ {
+ "core_id": 7,
+ "thread_id": 38
+ },
+ {
+ "core_id": 8,
+ "thread_id": 16
+ },
+ {
+ "core_id": 8,
+ "thread_id": 40
+ },
+ {
+ "core_id": 9,
+ "thread_id": 18
+ },
+ {
+ "core_id": 9,
+ "thread_id": 42
+ },
+ {
+ "core_id": 10,
+ "thread_id": 20
+ },
+ {
+ "core_id": 10,
+ "thread_id": 44
+ },
+ {
+ "core_id": 11,
+ "thread_id": 22
+ },
+ {
+ "core_id": 11,
+ "thread_id": 46
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p5p1",
+ "Mbps": 10000,
+ "pci": "0000:04:00.0",
+ "switch_port": "port0/16",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "02:ef:c9:38:c7:01",
+ "pci": "0000:04:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "92:04:e9:fe:c7:f9",
+ "pci": "0000:04:10.2",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "a2:41:32:78:25:48",
+ "pci": "0000:04:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "9e:65:ab:e8:a0:2b",
+ "pci": "0000:04:10.6",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "0a:38:88:4c:76:1b",
+ "pci": "0000:04:11.0",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "4e:8f:96:e1:d2:99",
+ "pci": "0000:04:11.2",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "62:b3:0a:15:1b:cc",
+ "pci": "0000:04:11.4",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "fa:29:6a:04:c3:fc",
+ "pci": "0000:04:11.6",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:33:16:f4"
+ },
+ {
+ "source_name": "p5p2",
+ "Mbps": 10000,
+ "pci": "0000:04:00.1",
+ "switch_port": "port0/17",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "1a:07:7d:f5:ab:bf",
+ "pci": "0000:04:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "12:63:9b:75:5c:02",
+ "pci": "0000:04:10.3",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "be:f8:54:de:8e:39",
+ "pci": "0000:04:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "82:bb:12:83:82:b9",
+ "pci": "0000:04:10.7",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "06:5c:e3:40:c3:e0",
+ "pci": "0000:04:11.1",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "32:54:10:77:36:d1",
+ "pci": "0000:04:11.3",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "0e:0f:3f:23:d6:17",
+ "pci": "0000:04:11.5",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "46:14:84:51:3d:ec",
+ "pci": "0000:04:11.7",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:33:16:f6"
+ },
+ {
+ "source_name": "p7p1",
+ "Mbps": 10000,
+ "pci": "0000:06:00.0",
+ "switch_port": "port0/18",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "d6:8a:a7:27:bc:7c",
+ "pci": "0000:06:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "9e:f4:67:21:28:12",
+ "pci": "0000:06:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "b6:f8:b7:c8:ae:07",
+ "pci": "0000:06:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "0e:e8:34:7a:3b:c4",
+ "pci": "0000:06:10.6",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "82:5d:0f:7a:20:91",
+ "pci": "0000:06:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "f2:6b:6a:4a:e3:93",
+ "pci": "0000:06:11.2",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "b2:d6:af:b0:12:f4",
+ "pci": "0000:06:11.4",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "22:0b:01:19:dd:3c",
+ "pci": "0000:06:11.6",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:33:0f:1c"
+ },
+ {
+ "source_name": "p7p2",
+ "Mbps": 10000,
+ "pci": "0000:06:00.1",
+ "switch_port": "port0/19",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "0a:2f:77:8f:53:da",
+ "pci": "0000:06:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "a2:f5:63:1d:1c:4d",
+ "pci": "0000:06:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "86:80:6c:d8:da:e0",
+ "pci": "0000:06:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "5a:9d:1c:19:3f:07",
+ "pci": "0000:06:10.7",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "ea:a9:fb:95:29:34",
+ "pci": "0000:06:11.1",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "0e:3a:e9:65:5b:d3",
+ "pci": "0000:06:11.3",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "ce:cd:53:c6:7b:ca",
+ "pci": "0000:06:11.5",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "52:b1:4e:91:34:8c",
+ "pci": "0000:06:11.7",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:33:0f:1e"
+ }
+ ],
+ "numa_socket": 0,
+ "hugepages": 60,
+ "memory": 64
+ }
+ ]
+}
+}
--- /dev/null
+{
+ "host":{
+ "name": "fake-host-3",
+ "user": "user",
+ "password": "password",
+ "ip_name": "fakehost3"
+ },
+"host-data":
+{
+ "name": "fake-host-3",
+ "ranking": 300,
+ "description": "fake host 3 for test mode",
+ "ip_name": "fakehost3",
+ "features": "lps,dioc,hwsv,ht,64b,tlbps",
+ "user": "user",
+ "password": "password",
+ "numas": [
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 1,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 25,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 3
+ },
+ {
+ "core_id": 1,
+ "thread_id": 27
+ },
+ {
+ "core_id": 2,
+ "thread_id": 5
+ },
+ {
+ "core_id": 2,
+ "thread_id": 29
+ },
+ {
+ "core_id": 3,
+ "thread_id": 7
+ },
+ {
+ "core_id": 3,
+ "thread_id": 31
+ },
+ {
+ "core_id": 4,
+ "thread_id": 9
+ },
+ {
+ "core_id": 4,
+ "thread_id": 33
+ },
+ {
+ "core_id": 5,
+ "thread_id": 11
+ },
+ {
+ "core_id": 5,
+ "thread_id": 35
+ },
+ {
+ "core_id": 6,
+ "thread_id": 13
+ },
+ {
+ "core_id": 6,
+ "thread_id": 37
+ },
+ {
+ "core_id": 7,
+ "thread_id": 15
+ },
+ {
+ "core_id": 7,
+ "thread_id": 39
+ },
+ {
+ "core_id": 8,
+ "thread_id": 17
+ },
+ {
+ "core_id": 8,
+ "thread_id": 41
+ },
+ {
+ "core_id": 9,
+ "thread_id": 19
+ },
+ {
+ "core_id": 9,
+ "thread_id": 43
+ },
+ {
+ "core_id": 10,
+ "thread_id": 21
+ },
+ {
+ "core_id": 10,
+ "thread_id": 45
+ },
+ {
+ "core_id": 11,
+ "thread_id": 23
+ },
+ {
+ "core_id": 11,
+ "thread_id": 47
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p2p1",
+ "Mbps": 10000,
+ "pci": "0000:44:00.0",
+ "switch_port": "port1/0",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "ce:24:06:7d:7a:5b",
+ "pci": "0000:44:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "a6:2d:10:75:dc:06",
+ "pci": "0000:44:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "4a:39:f1:d1:aa:46",
+ "pci": "0000:44:10.4",
+ "source_name": 2,
+ "vlan": 104
+ },
+ {
+ "mac": "ee:df:34:b3:cf:50",
+ "pci": "0000:44:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "46:02:47:d8:f5:66",
+ "pci": "0000:44:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "a6:11:78:97:b4:ab",
+ "pci": "0000:44:11.2",
+ "source_name": 5,
+ "vlan": 100
+ },
+ {
+ "mac": "36:c0:13:a4:4c:77",
+ "pci": "0000:44:11.4",
+ "source_name": 6,
+ "vlan": 105
+ },
+ {
+ "mac": "b6:3b:b5:43:3a:44",
+ "pci": "0000:44:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:25:97:f4"
+ },
+ {
+ "source_name": "p2p2",
+ "Mbps": 10000,
+ "pci": "0000:44:00.1",
+ "switch_port": "port1/1",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "f6:38:e9:5e:f9:42",
+ "pci": "0000:44:10.1",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "f6:1e:b9:d3:15:69",
+ "pci": "0000:44:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "2e:22:7f:44:86:e0",
+ "pci": "0000:44:10.5",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "5a:3f:db:66:d6:90",
+ "pci": "0000:44:10.7",
+ "source_name": 3,
+ "vlan": 104
+ },
+ {
+ "mac": "b2:05:b8:f3:ae:20",
+ "pci": "0000:44:11.1",
+ "source_name": 4,
+ "vlan": 101
+ },
+ {
+ "mac": "fa:fe:24:82:12:b5",
+ "pci": "0000:44:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "36:2a:0e:cb:29:01",
+ "pci": "0000:44:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "46:71:c0:8a:9c:48",
+ "pci": "0000:44:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:25:97:f6"
+ },
+ {
+ "source_name": "p3p2",
+ "Mbps": 10000,
+ "pci": "0000:43:00.1",
+ "switch_port": "port1/3",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "72:fd:7a:af:91:57",
+ "pci": "0000:43:10.1",
+ "source_name": 0,
+ "vlan": 103
+ },
+ {
+ "mac": "fa:c4:d7:e1:3b:07",
+ "pci": "0000:43:10.3",
+ "source_name": 1,
+ "vlan": 100
+ },
+ {
+ "mac": "c2:03:28:4a:0a:8e",
+ "pci": "0000:43:10.5",
+ "source_name": 2,
+ "vlan": 101
+ },
+ {
+ "mac": "66:1b:78:65:67:e2",
+ "pci": "0000:43:10.7",
+ "source_name": 3,
+ "vlan": 102
+ },
+ {
+ "mac": "be:4b:e9:77:ff:a2",
+ "pci": "0000:43:11.1",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "02:9a:94:e2:79:c0",
+ "pci": "0000:43:11.3",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "b2:80:62:dd:09:ea",
+ "pci": "0000:43:11.5",
+ "source_name": 6,
+ "vlan": 106
+ },
+ {
+ "mac": "ea:41:30:8e:af:b9",
+ "pci": "0000:43:11.7",
+ "source_name": 7,
+ "vlan": 107
+ }
+ ],
+ "mac": "a0:36:9f:25:97:de"
+ },
+ {
+ "source_name": "p3p1",
+ "Mbps": 10000,
+ "pci": "0000:43:00.0",
+ "switch_port": "port1/2",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "66:80:a0:55:83:93",
+ "pci": "0000:43:10.0",
+ "source_name": 0,
+ "vlan": 102
+ },
+ {
+ "mac": "fe:fd:72:b8:fe:bb",
+ "pci": "0000:43:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "6a:70:92:7f:6b:7b",
+ "pci": "0000:43:10.4",
+ "source_name": 2,
+ "vlan": 100
+ },
+ {
+ "mac": "3a:14:75:5e:63:15",
+ "pci": "0000:43:10.6",
+ "source_name": 3,
+ "vlan": 101
+ },
+ {
+ "mac": "2e:c9:5d:6e:27:48",
+ "pci": "0000:43:11.0",
+ "source_name": 4,
+ "vlan": 104
+ },
+ {
+ "mac": "02:f7:bb:82:78:1f",
+ "pci": "0000:43:11.2",
+ "source_name": 5,
+ "vlan": 105
+ },
+ {
+ "mac": "0e:5e:b6:a8:6c:80",
+ "pci": "0000:43:11.4",
+ "source_name": 6,
+ "vlan": 107
+ },
+ {
+ "mac": "8e:bf:81:bc:cb:44",
+ "pci": "0000:43:11.6",
+ "source_name": 7,
+ "vlan": 106
+ }
+ ],
+ "mac": "a0:36:9f:25:97:dc"
+ }
+ ],
+ "numa_socket": 1,
+ "hugepages": 28,
+ "memory": 32
+ },
+ {
+ "cores": [
+ {
+ "core_id": 0,
+ "thread_id": 0,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 0,
+ "thread_id": 24,
+ "status": "noteligible"
+ },
+ {
+ "core_id": 1,
+ "thread_id": 2
+ },
+ {
+ "core_id": 1,
+ "thread_id": 26
+ },
+ {
+ "core_id": 2,
+ "thread_id": 4
+ },
+ {
+ "core_id": 2,
+ "thread_id": 28
+ },
+ {
+ "core_id": 3,
+ "thread_id": 6
+ },
+ {
+ "core_id": 3,
+ "thread_id": 30
+ },
+ {
+ "core_id": 4,
+ "thread_id": 8
+ },
+ {
+ "core_id": 4,
+ "thread_id": 32
+ },
+ {
+ "core_id": 5,
+ "thread_id": 10
+ },
+ {
+ "core_id": 5,
+ "thread_id": 34
+ },
+ {
+ "core_id": 6,
+ "thread_id": 12
+ },
+ {
+ "core_id": 6,
+ "thread_id": 36
+ },
+ {
+ "core_id": 7,
+ "thread_id": 14
+ },
+ {
+ "core_id": 7,
+ "thread_id": 38
+ },
+ {
+ "core_id": 8,
+ "thread_id": 16
+ },
+ {
+ "core_id": 8,
+ "thread_id": 40
+ },
+ {
+ "core_id": 9,
+ "thread_id": 18
+ },
+ {
+ "core_id": 9,
+ "thread_id": 42
+ },
+ {
+ "core_id": 10,
+ "thread_id": 20
+ },
+ {
+ "core_id": 10,
+ "thread_id": 44
+ },
+ {
+ "core_id": 11,
+ "thread_id": 22
+ },
+ {
+ "core_id": 11,
+ "thread_id": 46
+ }
+ ],
+ "interfaces": [
+ {
+ "source_name": "p5p1",
+ "Mbps": 10000,
+ "pci": "0000:04:00.0",
+ "switch_port": "port1/4",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "aa:ef:2c:23:ae:04",
+ "pci": "0000:04:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "2e:d5:b9:3e:c4:8b",
+ "pci": "0000:04:10.2",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "4e:d6:55:09:17:1e",
+ "pci": "0000:04:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "ce:42:cc:9c:5a:32",
+ "pci": "0000:04:10.6",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "1e:71:34:7f:5d:47",
+ "pci": "0000:04:11.0",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "26:5c:e4:db:8c:48",
+ "pci": "0000:04:11.2",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "5e:48:23:56:63:c0",
+ "pci": "0000:04:11.4",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "9a:a2:fa:97:19:84",
+ "pci": "0000:04:11.6",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:27:46:a0"
+ },
+ {
+ "source_name": "p5p2",
+ "Mbps": 10000,
+ "pci": "0000:04:00.1",
+ "switch_port": "port1/5",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "0a:9b:b5:d1:e6:34",
+ "pci": "0000:04:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "5e:87:4a:22:e9:57",
+ "pci": "0000:04:10.3",
+ "source_name": 1,
+ "vlan": 105
+ },
+ {
+ "mac": "ea:94:d5:f6:ee:bb",
+ "pci": "0000:04:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "5a:3f:17:6b:bb:8b",
+ "pci": "0000:04:10.7",
+ "source_name": 3,
+ "vlan": 107
+ },
+ {
+ "mac": "8e:af:5c:cc:c8:82",
+ "pci": "0000:04:11.1",
+ "source_name": 4,
+ "vlan": 102
+ },
+ {
+ "mac": "3e:a0:df:f0:2b:07",
+ "pci": "0000:04:11.3",
+ "source_name": 5,
+ "vlan": 103
+ },
+ {
+ "mac": "ae:7d:00:9b:54:4d",
+ "pci": "0000:04:11.5",
+ "source_name": 6,
+ "vlan": 100
+ },
+ {
+ "mac": "fa:b5:0d:c6:78:87",
+ "pci": "0000:04:11.7",
+ "source_name": 7,
+ "vlan": 101
+ }
+ ],
+ "mac": "a0:36:9f:27:46:a2"
+ },
+ {
+ "source_name": "p7p1",
+ "Mbps": 10000,
+ "pci": "0000:06:00.0",
+ "switch_port": "port1/6",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "22:4a:21:37:84:80",
+ "pci": "0000:06:10.0",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "b2:8f:2c:04:08:08",
+ "pci": "0000:06:10.2",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "fe:2e:71:1f:99:dd",
+ "pci": "0000:06:10.4",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "1a:2c:7c:98:39:b6",
+ "pci": "0000:06:10.6",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "6e:39:08:a9:7e:b1",
+ "pci": "0000:06:11.0",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "4a:00:f3:5b:15:27",
+ "pci": "0000:06:11.2",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "b2:c3:48:da:24:55",
+ "pci": "0000:06:11.4",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "72:85:cd:18:ee:5d",
+ "pci": "0000:06:11.6",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:27:45:80"
+ },
+ {
+ "source_name": "p7p2",
+ "Mbps": 10000,
+ "pci": "0000:06:00.1",
+ "switch_port": "port1/7",
+ "switch_dpid": "00:01:02:03:04:05:06:07",
+ "sriovs": [
+ {
+ "mac": "0a:60:1a:9d:ba:e2",
+ "pci": "0000:06:10.1",
+ "source_name": 0,
+ "vlan": 104
+ },
+ {
+ "mac": "72:cb:0a:99:2f:7e",
+ "pci": "0000:06:10.3",
+ "source_name": 1,
+ "vlan": 103
+ },
+ {
+ "mac": "f2:2b:6b:d8:95:aa",
+ "pci": "0000:06:10.5",
+ "source_name": 2,
+ "vlan": 106
+ },
+ {
+ "mac": "b2:3a:4a:a8:c9:e9",
+ "pci": "0000:06:10.7",
+ "source_name": 3,
+ "vlan": 105
+ },
+ {
+ "mac": "06:85:75:b1:e8:10",
+ "pci": "0000:06:11.1",
+ "source_name": 4,
+ "vlan": 107
+ },
+ {
+ "mac": "12:41:69:61:89:3a",
+ "pci": "0000:06:11.3",
+ "source_name": 5,
+ "vlan": 102
+ },
+ {
+ "mac": "86:03:a8:5a:ad:71",
+ "pci": "0000:06:11.5",
+ "source_name": 6,
+ "vlan": 101
+ },
+ {
+ "mac": "ca:9c:5c:ad:e2:e2",
+ "pci": "0000:06:11.7",
+ "source_name": 7,
+ "vlan": 100
+ }
+ ],
+ "mac": "a0:36:9f:27:45:82"
+ }
+ ],
+ "numa_socket": 0,
+ "hugepages": 28,
+ "memory": 32
+ }
+ ]
+}
+}
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+{
+ "${}":[
+ "${name} host name",
+ "${user} host user (n2)",
+ "${ip_name} host access ip or name (${name}.hi.inet)",
+ "${description} host description (${name})"
+ ],
+
+ "host":{
+ "name": "${name}",
+ "user": "${user}",
+ "ip_name": "${ip_name}",
+ "description": "${description}"
+ }
+}
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+{
+ "${}":[
+ "${name} image name",
+ "${path} image path (/mnt/powervault/virtualization/vnfs/os/US1404user.qcow2)",
+ "${description} image description (${name})"
+ ],
+"image":{
+ "name":"${name}",
+ "description":"${description}",
+ "path":"${path}"
+}
+}
--- /dev/null
+network:
+ name: default
+ type: bridge_man
+ provider:physical: default
+ shared: true
+
--- /dev/null
+network:
+ name: macvtap:em1
+ type: bridge_man
+ provider:physical: macvtap:em1
+ shared: true
+
--- /dev/null
+network:
+ name: shared_bridge_net
+ type: bridge_data
+ provider:physical: bridge:virbrMan1
+ shared: true
+
--- /dev/null
+network:
+ name: data_net
+ type: data
+
+ #if you want to connect this network to a concrete switch port for outside connectivity
+ #indicate it at provider_physical with openflow:<switch_port>[:vlan]
+ #<switch_port> must be a valid openflow port (one of the listed with openvim openflow-port-list)
+ #add [:vlan] without spaces if this port must be vlan tagged. If missing it is not tagged
+
+ #provider:vlan contains the vlan used by the SRIOV interfaces connected to this network
+ #it always contain a value regardless used or not. If missing openvim will assign a value
+
+ provider:physical: openflow:port1/8:vlan
+ provider:vlan: 3001
+ shared: true
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+{
+ "${}":[
+ "${name} provide a network name",
+ "${type} provide a type: bridge_data,bridge_man,data,ptp (ptp)"
+ ],
+
+ "network":{
+ "name": "${name}",
+ "type": "${type}"
+ }
+}
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+
+{
+ "${}":[
+ "${name} provide a port name",
+ "${net_id} provide the network uuid (null):",
+ "${vlan} provide the vlan if any (null):",
+ "${port} provide the attached switch port (Te0/47)",
+ "${mac} provide the mac of external device if known (null):"
+ ],
+
+ "port":{
+ "name": "${name}",
+ "network_id": "${net_id null}",
+ "type": "external",
+ "binding:vlan": "${vlan null-int}",
+ "binding:switch_port": "${port}",
+ "mac_address": "${mac null}"
+ }
+}
+
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+{
+ "${}":[
+ "${name} provide a name (VM)",
+ "${description} provide a description (${name})",
+ "${image_id} provide image_id uuid",
+ "${flavor_id} provide flavor_id uuid",
+ "${network0} provide a bridge network id; enter='default' (00000000-0000-0000-0000-000000000000)",
+ "${network1} provide a bridge network id; enter='virbrMan' (60f5227e-195f-11e4-836d-52540030594e)"
+ ],
+"server":{
+ "networks":[
+ {
+ "name":"mgmt0",
+ "vpci": "0000:00:0a.0",
+ "uuid":"${network0}"
+ },
+ {
+ "name":"ge0",
+ "vpci": "0000:00:0b.0",
+ "uuid":"${network1}"
+ }
+ ],
+ "name":"${name}",
+ "description":"${description}",
+ "imageRef": "${image_id}",
+ "flavorRef": "${flavor_id}"
+}
+}
--- /dev/null
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+{
+ "${}":[
+ "${name} tenant name",
+ "${description} tenant description (${name})"
+ ],
+
+ "tenant":{
+ "name": "${name}",
+ "description": "${description}"
+ }
+}
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script to test openflow connector with the creation of rules
+#
+
+function usage(){
+ echo -e "usage: ${BASH_SOURCE[0]} [OPTIONS] \n test openflow connector "
+ echo -e " OPTIONS:"
+ echo -e " -f --force does not prompt for confirmation"
+ echo -e " -v --same-vlan use this if the parameter 'of_controller_nets_with_same_vlan'"
+ echo -e " is not false at openvimd.cfg to avoid test unrealizable openflow nets"
+ echo -e " -d --debug show debug information at each command. It is quite verbose"
+ echo -e " -h --help shows this help"
+}
+
+
+function delete_and_exit(){
+ echo
+ [[ $force != y ]] && read -e -p " Press enter to delete the deployed things " kk
+ echo
+ for f in $TODELETE
+ do
+ if [[ $f == restore ]]
+ then
+ printf "%-50s" "restoring back old rules: "
+ result=`openflow install ./test_openflow_old_rules.bk $debug `
+ if [[ $? != 0 ]]
+ then
+ echo "FAIL cannot install old rules:"
+ echo "$result"
+ else
+ rm ./test_openflow_old_rules.bk
+ echo OK "./test_openflow_old_rules.bk deleted"
+ fi
+ else
+ printf "%-50s" "removing $f rule: "
+ result=`openflow delete $f -f $debug`
+ [[ $? != 0 ]] && echo "FAIL cannot delete" && echo "$result" || echo OK
+ fi
+ done
+ exit $1
+}
+
+force=n
+same_vlan=n
+debug=""
+TODELETE=""
+#detect if is called with a source to use the 'exit'/'return' command for exiting
+[[ ${BASH_SOURCE[0]} != $0 ]] && echo "Do not execute this script as SOURCE" >&2 && return 1
+
+#check correct arguments
+for param in $*
+do
+ if [[ $param == -h ]] || [[ $param == --help ]]
+ then
+ usage
+ exit 0
+ elif [[ $param == -d ]] || [[ $param == --debug ]]
+ then
+ debug="--debug"
+ elif [[ $param == -v ]] || [[ $param == --same-vlan ]]
+ then
+ same_vlan=y
+ elif [[ $param == -f ]] || [[ $param == --force ]]
+ then
+ force=y
+ else
+ echo "invalid argument '$param'?. See $0 --help" && exit 1
+ fi
+done
+
+#detect if environment variables are set
+fail=""
+[[ -z $OF_CONTROLLER_TYPE ]] && echo "OF_CONTROLLER_TYPE not defined" >&2 && fail=1
+[[ -z $OF_CONTROLLER_IP ]] && echo "OF_CONTROLLER_IP not defined" >&2 && fail=1
+[[ -z $OF_CONTROLLER_PORT ]] && echo "OF_CONTROLLER_PORT not defined" >&2 && fail=1
+[[ -z $OF_CONTROLLER_DPID ]] && echo "OF_CONTROLLER_DPID not defined" >&2 && fail=1
+[[ -n $fail ]] && exit 1
+
+
+export _exit=delete_and_exit
+if [[ $force != y ]]
+then
+ echo " This will remove temporally the existing openflow rules and restored back a the end"
+ read -e -p "Press enter to continue, CTRL+C to abort " kk
+fi
+
+
+printf "%-50s" "obtain port list: "
+result=`openflow port-list $debug | gawk '/^ /{print substr($1,0,length($1)-1)}'`
+[[ $? != 0 ]] && echo "FAIL" && echo "$result" && $_exit 1
+ports=`echo $result | wc -w`
+[[ $ports -lt 4 ]] && echo "FAIL not enough ports managed by this DPID, needed at least 4" && $_exit 1
+echo OK $ports ports
+port0=`echo $result | cut -d" " -f1`
+port1=`echo $result | cut -d" " -f2`
+port2=`echo $result | cut -d" " -f3`
+port3=`echo $result | cut -d" " -f4`
+
+
+printf "%-50s" "saving the current rules: "
+openflow list $debug > ./test_openflow_old_rules.bk
+[[ $? != 0 ]] && echo "FAIL cannot obtain existing rules" && $_exit 1
+echo OK "> ./test_openflow_old_rules.bk"
+
+printf "%-50s" "clearing all current rules: "
+openflow clear -f $debug
+[[ $? != 0 ]] && echo "FAIL cannot clear existing rules" && $_exit 1
+result=`openflow list | wc -l`
+[[ $result != 1 ]] && echo "FAIL rules not completely cleared" && $_exit 1
+echo OK
+TODELETE="restore"
+
+printf "%-50s" "clearing again all rules: "
+openflow clear -f $debug
+[[ $? != 0 ]] && echo "FAIL when there are not any rules" && $_exit 1
+result=`openflow list | wc -l`
+[[ $result != 1 ]] && echo "FAIL rules not completely cleared" && $_exit 1
+echo OK
+TODELETE="restore"
+
+
+printf "%-50s" "new rule vlan,mac -> no vlan: "
+rule_name=fromVlanMac_to_NoVlan1
+openflow add $rule_name --priority 1000 --matchmac "aa:bb:cc:dd:ee:ff" --matchvlan 500 --inport $port0 --stripvlan --out $port1 $debug
+[[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1
+expected="$OF_CONTROLLER_DPID 1000 $rule_name $port0 aa:bb:cc:dd:ee:ff 500 vlan=None,out=$port1"
+result=`openflow list | grep $rule_name`
+[[ $? != 0 ]] && echo "FAIL rule bad inserted" && $_exit 1
+result=`echo $result` #remove blanks
+[[ "$result" != "$expected" ]] && echo "FAIL" && echo " expected: $expected\n obtained: $result" && $_exit 1
+echo OK $rule_name
+TODELETE="$rule_name $TODELETE"
+
+printf "%-50s" "new rule mac -> vlan: "
+rule_name=fromMac_to_Vlan2
+openflow add $rule_name --priority 1001 --matchmac "ff:ff:ff:ff:ff:ff" --inport $port1 --setvlan 501 --out $port2 --out $port3 $debug
+[[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1
+expected="$OF_CONTROLLER_DPID 1001 $rule_name $port1 ff:ff:ff:ff:ff:ff any vlan=501,out=$port2,out=$port3"
+result=`openflow list | grep $rule_name`
+[[ $? != 0 ]] && echo "FAIL rule bad inserted" && $_exit 1
+result=`echo $result` #remove blanks
+[[ "$result" != "$expected" ]] && echo "FAIL" && echo " expected: $expected\n obtained: $result" && $_exit 1
+echo OK $rule_name
+TODELETE="$rule_name $TODELETE"
+
+printf "%-50s" "new rule None -> None: "
+rule_name=fromNone_to_None
+openflow add $rule_name --priority 1002 --inport $port2 --out $port0 $debug
+[[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1
+expected="$OF_CONTROLLER_DPID 1002 $rule_name $port2 any any out=$port0"
+result=`openflow list | grep $rule_name`
+[[ $? != 0 ]] && echo "FAIL rule bad inserted" && $_exit 1
+result=`echo $result` #remove blanks
+[[ "$result" != "$expected" ]] && echo "FAIL" && echo " expected: $expected\n obtained: $result" && $_exit 1
+echo OK $rule_name
+TODELETE="$rule_name $TODELETE"
+
+printf "%-50s" "new rule vlan -> vlan: "
+rule_name=fromVlan_to_Vlan1
+openflow add $rule_name --priority 1003 --matchvlan 504 --inport $port3 --setvlan 505 --out $port0 $debug
+[[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1
+expected="$OF_CONTROLLER_DPID 1003 $rule_name $port3 any 504 vlan=505,out=$port0"
+result=`openflow list | grep $rule_name`
+[[ $? != 0 ]] && echo "FAIL rule bad inserted" && $_exit 1
+result=`echo $result` #remove blanks
+[[ "$result" != "$expected" ]] && echo "FAIL" && echo " expected: $expected\n obtained: $result" && $_exit 1
+echo OK $rule_name
+TODELETE="$rule_name $TODELETE"
+
+
+if [[ $same_vlan == n ]]
+then
+
+ printf "%-50s" "new rule Vlan -> Vlan_Vlan: "
+ rule_name=fromVlan_to_Vlan1Vlan1
+ openflow add $rule_name --priority 1005 --inport $port3 --matchvlan 505 --setvlan 510 --out $port0 --setvlan 511 --out $port1 --stripvlan --out=$port2 $debug
+ [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1
+ expected="$OF_CONTROLLER_DPID 1005 $rule_name $port3 any 505 vlan=510,out=$port0,vlan=511,out=$port1,vlan=None,out=$port2"
+ result=`openflow list | grep $rule_name`
+ [[ $? != 0 ]] && echo "FAIL rule bad inserted" && $_exit 1
+ result=`echo $result` #remove blanks
+ [[ "$result" != "$expected" ]] && echo "FAIL" && echo " expected: $expected\n obtained: $result" && $_exit 1
+ echo OK $rule_name
+ TODELETE="$rule_name $TODELETE"
+
+fi
+
+echo
+echo DONE
+
+$_exit 0
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This is a client tester for openvim.
+It is almost DEPRECATED by the openvim client
+
+The reason for keeping is because it is used for some scripts
+and it contain the -r option (delete recursive)
+that it is very useful for deleting content of database.
+Another difference from openvim is that it is more verbose
+and so more suitable for the developers
+'''
+
+__author__="Alfonso Tierno"
+__date__ ="$5-oct-2014 11:09:29$"
+
+import requests
+import json
+import yaml
+import sys
+import getopt
+from jsonschema import validate as js_v, exceptions as js_e
+
+version="0.0.2"
+global global_config
+
+
+def get_elements(url):
+ headers_req = {'content-type': 'application/json'}
+ try:
+ vim_response = requests.get(url, headers = headers_req)
+ #print vim_response
+ #print vim_response.status_code
+ if vim_response.status_code == 200:
+ #print vim_response.json()
+ #print json.dumps(vim_response.json(), indent=4)
+ content = vim_response.json()
+ return 1, content
+ #print http_content
+ else:
+ text = " Error. VIM response '%s': not possible to GET %s" % (vim_response.status_code, url)
+ text += "\n " + vim_response.text
+ #print text
+ return -vim_response.status_code,text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception "+ str(e.message)
+
+def delete_elements(url):
+ headers_req = {'content-type': 'application/json'}
+
+ try:
+ vim_response = requests.delete(url, headers = headers_req)
+ #print vim_response
+ #print vim_response.status_code
+ if vim_response.status_code == 200:
+ pass
+ #print vim_response.json()
+ #print json.dumps(vim_response.json(), indent=4)
+ else:
+ #print vim_response.text
+ text = " Error. VIM response '%s': not possible to DELETE %s" % (vim_response.status_code, url)
+ text += "\n " + vim_response.text
+ #print text
+ return -vim_response.status_code,text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception "+ str(e.message)
+ return 1, None
+
+
+def new_elements(url, payload):
+ headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+ #print str(payload)
+ try:
+ vim_response = requests.post(url, data=json.dumps(payload), headers=headers_req)
+ #print vim_response
+ #print vim_response.status_code
+ if vim_response.status_code == 200:
+ #print vim_response.json()
+ #print json.dumps(vim_response.json(), indent=4)
+ return 1, vim_response.text
+ else:
+ #print vim_response.text
+ text = "Error. VIM response '%s': not possible to ADD %s" % (vim_response.status_code, url)
+ text += "\n" + vim_response.text
+ #print text
+ return -vim_response.status_code,text
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception "+ str(e.message)
+
+
+def get_details(url, what, c):
+ item_list = []
+ return_dict = {what+'s': []}
+
+ item = c.get(what,None)
+ if item is None: item = c.get(what+'s',None)
+ if item is None:
+ error_text= " Internal error, not found '" + what +"[s]' in content"
+ print 'get_details()', error_text, c
+ return -1, error_text
+ if type(item) is list:
+ item_list = item
+ else:
+ item_list.append(item)
+ if len(item_list)==0:
+ print what, "not found"
+ return 1
+ for item in item_list:
+ uuid = item.get('id',None)
+ if uuid is None: uuid = item.get('uuid',None)
+ if uuid is None:
+ error_text= " Internal error, not found 'id/uuid' in item"
+ print 'get_details()', error_text, item
+ return -1, error_text
+ #print " get", what, uuid, " >>>>>>>> ",
+ r,c = get_elements(url + "/" + uuid)
+ if r<0:
+ # print "fail"
+ print " get", what, uuid, "fail", c
+ return -1, c
+ #else:
+ # print 'ok'
+ return_dict[what+'s'].append(c[what])
+ return 1, return_dict
+
+
+def action_details(url, what, c, force, payload):
+ item_list = []
+ return_dict = {what+'s': []}
+ headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+ fail=0
+ ok=0
+
+ #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what
+
+ item = c.get(what,None)
+ if item is None: item = c.get(what+'s',None)
+ if item is None:
+ error_text= " Internal error, not found '" + what +"[s]' in content"
+ print 'get_details()', error_text, c
+ return -1, error_text
+ if type(item) is list:
+ item_list = item
+ else:
+ item_list.append(item)
+ if len(item_list)==0:
+ print what, "not found"
+ return 1
+ for item in item_list:
+ name = item.get('name',None)
+ uuid = item.get('id',None)
+ if uuid is None: uuid = item.get('uuid',None)
+ if uuid is None:
+ error_text= " Internal error, not found 'id/uuid' in item"
+ print 'get_details()', error_text, item
+ return -1, error_text
+ if not force:
+ r = raw_input("Action on " + what + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ print " put", what, uuid, " >>>>>>>> ",
+ else:
+ continue
+
+ #print str(payload)
+ try:
+ vim_response = requests.post(url + "/" + uuid + "/action", data=json.dumps(payload), headers=headers_req)
+ if vim_response.status_code == 200:
+ print 'ok'
+ ok += 1
+ return_dict[what+'s'].append(vim_response.json())
+ return_dict[what+'s'][-1]['uuid'] = uuid
+ return_dict[what+'s'][-1]['name'] = name
+ else:
+ fail += 1
+ print "fail"
+ #print vim_response.text
+ #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url)
+ #text += "\n" + vim_response.text
+ #print text
+ error_dict = vim_response.json()
+ error_dict['error']['uuid']=uuid
+ error_dict['error']['name']=name
+ return_dict[what+'s'].append(error_dict)
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception "+ str(e.message)
+ if ok>0 and fail>0: return 0, return_dict
+ elif fail==0 : return 1, return_dict
+ else: return -1, return_dict
+
+
+
+def edit_details(url, what, c, force, payload):
+ item_list = []
+ return_dict = {what+'s': []}
+ headers_req = {'Accept': 'application/json', 'content-type': 'application/json'}
+ fail=0
+ ok=0
+
+ #Allows for payload both keypairs inside a 'server','port' ... or directly. In later case, put keypairs inside what
+ if what not in payload:
+ payload = {what:payload}
+
+ item = c.get(what,None)
+ if item is None: item = c.get(what+'s',None)
+ if item is None:
+ error_text= " Internal error, not found '" + what +"[s]' in content"
+ print 'get_details()', error_text, c
+ return -1, error_text
+ if type(item) is list:
+ item_list = item
+ else:
+ item_list.append(item)
+ if len(item_list)==0:
+ print what, "not found"
+ return 1
+ for item in item_list:
+ name = item.get('name',None)
+ uuid = item.get('id',None)
+ if uuid is None: uuid = item.get('uuid',None)
+ if uuid is None:
+ error_text= " Internal error, not found 'id/uuid' in item"
+ print 'get_details()', error_text, item
+ return -1, error_text
+ if not force:
+ r = raw_input("Edit " + what + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ print " put", what, uuid, " >>>>>>>> ",
+ else:
+ continue
+
+ #print str(payload)
+ try:
+ vim_response = requests.put(url + "/" + uuid, data=json.dumps(payload), headers=headers_req)
+ if vim_response.status_code == 200:
+ print 'ok'
+ ok += 1
+ return_dict[what+'s'].append( vim_response.json()[what] )
+ else:
+ fail += 1
+ print "fail"
+ #print vim_response.text
+ #text = "Error. VIM response '%s': not possible to PUT %s" % (vim_response.status_code, url)
+ #text += "\n" + vim_response.text
+ #print text
+ error_dict = vim_response.json()
+ error_dict['error']['uuid']=uuid
+ error_dict['error']['name']=name
+ return_dict[what+'s'].append(error_dict)
+ except requests.exceptions.RequestException, e:
+ return -1, " Exception "+ str(e.message)
+ if ok>0 and fail>0: return 0, return_dict
+ elif fail==0 : return 1, return_dict
+ else: return -1, return_dict
+
+def get_del_recursive(url, what, url_suffix, force=False, recursive=False):
+ #print
+ #print " get", what, a, " >>>>>>>> ",
+ r,c = get_elements(url + what + 's' + url_suffix)
+ if r<0:
+ print c, "when getting", what, url_suffix
+ return -1
+ # print "ok"
+
+ list_todelete = c.get(what, None)
+ if list_todelete is None: list_todelete = c.get(what+'s', None)
+ if list_todelete is None:
+ print " Internal error, not found '" + what +"[s]' in", c
+ return -3, " Internal error, not found a valid dictionary"
+ if type(list_todelete) == dict:
+ list_todelete = (list_todelete, )
+
+ if len(list_todelete)==0:
+ print what, url_suffix, "not found"
+ return 1
+ for c in list_todelete:
+ uuid=c.get('id', None)
+ if uuid is None:
+ uuid=c.get('uuid', None)
+ if uuid is None:
+ print "Id not found"
+ continue
+ name = c.get("name","")
+ if recursive:
+ if what=='tenant' :
+ get_del_recursive(url + uuid + "/", 'server', "", force, recursive)
+ get_del_recursive(url + uuid + "/", 'flavor', "", force, recursive)
+ get_del_recursive(url + uuid + "/", 'image', "", force, recursive)
+ get_del_recursive(url, 'network', "?tenant_id="+uuid, force, recursive)
+ elif what=='flavors' :
+ #get_del_recursive(url, 'servers', "?flavorRef="+uuid, force, recursive)
+ pass
+ elif what=='image' :
+ get_del_recursive(url, 'server', "?imageRef="+uuid, force, recursive)
+ elif what=='hosts' :
+ get_del_recursive(url, 'server', "?hostId="+uuid, force, recursive)
+
+ if not force:
+ r = raw_input("Delete " + what + " " + uuid + " " + name + " (y/N)? ")
+ if len(r)>0 and r[0].lower()=="y":
+ pass
+ else:
+ continue
+ r,c = delete_elements(url + what + "s/" + uuid)
+ if r<0:
+ #print "Error deleting", vimURI, -r
+ print c
+ else:
+ print what, uuid, name, "deleted"
+ return 1
+
+def check_valid_uuid(uuid):
+ id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
+ try:
+ js_v(uuid, id_schema)
+ return True
+ except js_e.ValidationError:
+ return False
+
+def change_string(text, var_list):
+ end=0
+ type_=None
+ while True:
+ ini = text.find("${", end)
+ if ini<0: return text
+ end = text.find("}", ini)
+ if end<0: return text
+ end+=1
+
+ var = text[ini:end]
+ if ' ' in var:
+ kk=var.split(" ")
+ var=kk[0]+"}"
+ type_=kk[-1][:-1]
+ var = var_list.get(var, None)
+ if var==None: return text
+
+ text = text[:ini] + var + text[end:]
+ if type_ != None:
+ if 'null' in type_ and text=="null":
+ return None
+ if 'int' in type_ : #and text.isnumeric():
+ return int(text)
+ return text
+
+def chage_var_recursively(data, var_list):
+ '''Check recursively the conent of data, and look for "*${*}*" variables and changes
+ It assumes that this variables are not in the key of dictionary,
+ Attributes:
+ 'data': dictionary, or list. None or empty is consideted valid
+ 'var_list': dictionary (name:change) pairs
+ Return:
+ None, data is modified
+ '''
+
+ if type(data) is dict:
+ for k in data.keys():
+ if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
+ chage_var_recursively(data[k], var_list)
+ elif type(data[k]) is str:
+ data[k] = change_string(data[k], var_list)
+ if type(data) is list:
+ for k in range(0,len(data)):
+ if type(data[k]) is dict or type(data[k]) is list:
+ chage_var_recursively(data[k], var_list)
+ elif type(data[k]) is str:
+ data[k] = change_string(data[k], var_list)
+
+def change_var(data):
+ if type(data) is not dict:
+ return -1, "Format error, not a object (dictionary)"
+ if "${}" not in data:
+ return 0, data
+
+ var_list={}
+ for var in data["${}"]:
+ r = var.find("}",) + 1
+ if r<=2 or var[:2] != '${':
+ return -1, "Format error at '${}':" + var
+ #change variables inside description text
+ if "${" in var[r:]:
+ var = var[:r] + change_string(var[r:], var_list)
+ d_start = var.rfind("(",) + 1
+ d_end = var.rfind(")",)
+ if d_start>0 and d_end>=d_start:
+ default = var[d_start:d_end]
+ else: default=None
+ v = raw_input(var[r:] + "? ")
+ if v=="":
+ if default != None:
+ v = default
+ else:
+ v = raw_input(" empty string? try again: ")
+ var_list[ var[:r] ] = str(v)
+
+ del data["${}"]
+ chage_var_recursively(data, var_list)
+ return 0, data
+
+def parse_yaml_json(text):
+ try:
+ data = yaml.load(text)
+ return 0, data
+ except yaml.YAMLError, exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ return -1, " Error yaml/json format error at " + error_pos
+
+def load_file(file_, parse=False):
+ try:
+ f = open(file_, 'r')
+ read_data = f.read()
+ f.close()
+ if not parse:
+ return 0, read_data
+ except IOError, e:
+ return -1, " Error opening file '" + file_ + "': " + e.args[1]
+
+ try:
+ data = yaml.load(read_data)
+ return change_var(data)
+ except yaml.YAMLError, exc:
+ error_pos = ""
+ if hasattr(exc, 'problem_mark'):
+ mark = exc.problem_mark
+ error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
+ return -2, " Error yaml/json format error at '"+ file_ +"'"+error_pos
+
+def load_configuration(configuration_file):
+ default_tokens ={'http_port':8080, 'http_host':'localhost', 'test_mode':False, 'of_controller_nets_with_same_vlan':True}
+
+ r, config = load_file(configuration_file, parse=True)
+ if r < 0:
+ return False, config
+
+ #Check default values tokens
+ for k,v in default_tokens.items():
+ if k not in config: config[k]=v
+
+ return (True, config)
+
+items_list = ('server','host','tenant','image','flavor','network','port')
+action_list = ('list','get','new','del','edit','action')
+
+
+def usage(complete=False):
+ global items_list
+ global action_list
+ print "Usage: ", sys.argv[0], "[options]", " [" + ",".join(action_list) +"] ", "<item> [<other>] "
+ print " Perform an test action over openvim"
+ print " "+",".join(action_list)+": List (by default), GET detais, Creates, Deletes, Edit"
+ print " <item>: can be one of " + ",".join(items_list)
+ print " <other>: list of uuid|name for 'get|del'; list of json/yaml files for 'new' or 'edit'"
+ if not complete:
+ print " Type -h or --help for a complete list of options"
+ return
+ print " Options:"
+ print " -v|--version: prints current version"
+ print " -c|--config [configuration_file]: loads the configuration file (default: openvimd.cfg)"
+ print " -h|--help: shows this help"
+ print " -u|--url [URL]: url to use instead of the one loaded from configuration file"
+ print " -t|--tenant [tenant uuid]: tenant to be used for some comands. IF mising it will use the default obtained in configuration file"
+ print " -F|--filter [A=B[&C=D...]: URL query string used for 'get' or 'del' commands"
+ print " -f|--force : Do not ask for confirmation when deleting. Also remove dependent objects."
+ print " -r|--recursive : Delete also dependency elements, (from tenants: images, flavors,server; from hosts: instances; ..."
+ print " Examples:"
+ print " ",sys.argv[0]," tenant #list tenants "
+ print " ",sys.argv[0]," -F'device_owner=external' get port #get details of all external ports"
+ print " ",sys.argv[0]," del server ses pan #delete server names 'ses' and 'pan'. Do not ask for confirmation"
+ print " ",sys.argv[0]," -r -f del host #delete all host and all the dependencies "
+ print " ",sys.argv[0]," new host ./Host/nfv100.json #add a host which information is in this file"
+ print " ",sys.argv[0]," edit network f348faf8-59ef-11e4-b4c7-52540030594e '{\"network\":{\"admin_state_up\":false}}'"
+ print " #change the admin status of this network"
+ return
+
+
+if __name__=="__main__":
+ global vimURI
+ global vimURI_admin
+
+ global what
+ global query_string
+#init variables
+ action="list"
+ what=None
+ url=None
+ query_string = ""
+ force = False
+ recursive = False
+ tenant = None
+ additional = []
+ #look for parent dir
+ config_file = '../openvimd.cfg'
+ pos = sys.argv[0].rfind("/")
+ if pos<0:
+ base_dir="./"
+ else:
+ base_dir = sys.argv[0] [:pos+1]
+ if pos>=0:
+ config_file = base_dir + config_file
+
+#get params
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hvrfc:u:t:F:",
+ ["config", "help", "version", "force", "filter","tenant","url","recursive"])
+ except getopt.GetoptError, err:
+ print " Error:", err # will print something like "option -a not recognized"
+ usage()
+ sys.exit(-2)
+
+ for o, a in opts:
+ if o in ("-v", "--version"):
+ print "test_openvim version", version, "Oct 2014"
+ print "(c) Copyright Telefonica"
+ sys.exit(0)
+ elif o in ("-h", "--help"):
+ usage(True)
+ sys.exit(0)
+ elif o in ("-c", "--config"): config_file = a
+ elif o in ("-f", "--force"): force = True
+ elif o in ("-r", "--recursive"): recursive = True
+ elif o in ("-F", "--filter"): query_string = "?"+a
+ elif o in ("-u", "--url"): url = a
+ elif o in ("-t", "--tenant"): tenant = a
+ else:
+ assert False, "Unhandled option"
+
+ for a in args:
+ if len(a) == 0:
+ print " Warning!!! Found an empty parameter?"
+ elif a[0]=="-":
+ print " Error!!! Put options parameter at the beginning"
+ sys.exit(-2)
+ elif what is not None:
+ additional.append(a)
+ elif a in items_list:
+ what=a
+ elif a[:-1] in items_list and a[-1]=='s':
+ what=a[:-1]
+ elif a in action_list:
+ action=a
+ else:
+ print " Missing <item>", ",".join(items_list)
+ sys.exit(-2)
+ if what is None:
+ usage()
+ sys.exit(-1)
+ #Load configuration file
+ r, config_dic = load_configuration(config_file)
+ #print config_dic
+ if not r:
+ print config_dic
+ config_dic={}
+ #exit(-1)
+
+ #override parameters obtained by command line
+ try:
+ if url is not None:
+ vimURI = vimURI_admin = url
+ else:
+ vimURI = "http://" + config_dic['http_host'] +":"+ str(config_dic['http_port']) + "/openvim/"
+ if 'http_admin_port' in config_dic:
+ vimURI_admin = "http://" + config_dic['http_host'] +":"+ str(config_dic['http_admin_port']) + "/openvim/"
+ except: #key error
+ print " Error: can not get URL; neither option --u,-url, nor reading configuration file"
+ exit(-1)
+ if tenant is None:
+ tenant = config_dic.get('tenant_id', None)
+
+#check enough parameters
+ URI=vimURI
+ if (what in ('host','port') and action in ('del','new')) or (what=='host' and action=='edit' ):
+ if vimURI_admin is None:
+ print " Error: Can not get admin URL; neither option -t,--tenant, nor reading configuration file"
+ exit(-1)
+ else:
+ URI=vimURI_admin
+ if URI[-1] != "/": URI+="/"
+ if what in ('server','image','flavor'):
+ if tenant is None:
+ print " Error: Can not get tenant; neither option -t,--tenant, nor reading configuration file"
+ exit(-1)
+ URI += tenant + "/"
+
+
+ exit_code=0
+ try:
+#load file for new/edit
+ payload_list=[]
+ if action=='new' or action=='edit' or action=='action':
+ if len(additional)==0:
+ if action=='new' :
+ additional.append(base_dir+what+"s/new_"+what+".yaml")
+ #print " New what? Missing additional parameters to complete action"
+ else:
+ print " What must be edited? Missing additional parameters to complete action"
+ exit(-1)
+ if action=='edit'or action=='action':
+ #obtain only last element
+ additional_temp = additional[:-1]
+ additional = additional[-1:]
+
+ for a in additional:
+ r,payload = load_file(a, parse=True)
+ if r<0:
+ if r==-1 and "{" in a or ":" in a:
+ #try to parse directly
+ r,payload = parse_yaml_json(a)
+ if r<0:
+ print payload
+ exit (-1)
+ else:
+ print payload
+ exit (-1)
+ payload_list.append(payload)
+ if action=='edit'or action=='action':
+ additional = additional_temp
+
+
+#perform actions NEW
+ if action=='new':
+ for payload in payload_list:
+ print "\n new", what, a, " >>>>>>>> ",
+ r,c = new_elements(URI+what+'s', payload)
+ if r>0:
+ print "ok"
+ else:
+ print "fail"
+ exit_code = -1
+ print c
+ #try to decode
+ exit(exit_code)
+
+ #perform actions GET LIST EDIT DEL
+ if len(additional)==0:
+ additional=[""]
+ for a in additional:
+ filter_qs = query_string
+ if a != "" :
+ if check_valid_uuid(a):
+ if len(filter_qs) > 0: filter_qs += "&" + "id=" + str(a)
+ else: filter_qs += "?" + "id=" + str(a)
+ else:
+ if len(filter_qs) > 0: filter_qs += "&" + "name=" + str(a)
+ else: filter_qs += "?" + "name=" + str(a)
+
+ if action=='list' or action=='get' or action=='edit'or action=='action':
+ url = URI + what+'s'
+ print url + filter_qs
+ #print " get", what, a, " >>>>>>>> ",
+ r,c = get_elements(url + filter_qs)
+ if r<0:
+ #print "fail"
+ exit_code = -1
+ print c
+ else:
+ #print "ok"
+ if action=='list':
+ print json.dumps(c, indent=4)
+ continue
+
+ if action=='get':
+ r1,c1 = get_details(url, what, c)
+ elif action=='action':
+ r1,c1 = action_details(url, what, c, force, payload_list[0])
+ else: # action=='edit':
+ r1,c1 = edit_details(url, what, c, force, payload_list[0])
+ if r1<0:
+ exit_code = -1
+ else:
+ if r>0: print "ok"
+ else: print "ok with some fails"
+ print json.dumps(c1, indent=4)
+
+ elif action=='del':
+ r = get_del_recursive(URI, what, filter_qs, force, recursive)
+ if r<0:
+ exit_code = -1
+ exit(exit_code)
+
+ except KeyboardInterrupt:
+ print " Canceled"
+
+
--- /dev/null
+#!/bin/bash
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+#
+#author Alfonso Tierno
+#
+#script to test openvim with the creation of flavors and interfaces, openflow rules
+#using images already inserted
+#
+
+function usage(){
+ echo -e "usage: ${BASH_SOURCE[0]} [OPTIONS] \n test openvim "
+ echo -e " OPTIONS:"
+ echo -e " -f --force does not prompt for confirmation"
+ echo -e " -v --same-vlan use this if the parameter 'of_controller_nets_with_same_vlan'"
+ echo -e " is not false at openvimd.cfg to avoid test unrealizable openflow nets"
+ echo -e " -h --help shows this help"
+ echo -e " -c --create create management network and two images (valid for test mode)"
+ echo
+ echo "This script test openvim, creating flavors, images, vms, de-attaching dataplane port"
+ echo "from one network to other and testing openflow generated rules."
+ echo "By default (unless -c option) uses and already created management network and two valid images."
+ echo "If -c option is set, it creates the network and images with fake content (only usefull for"
+ echo "openvim in 'test' mode) This is speccified in this shell variables:"
+ echo " VIM_TEST_NETWORK_INTERNET name of the mamagement network to use"
+ echo " VIM_TEST_IMAGE_PATH path of a vm image to use, the image is created if not exist"
+ echo " VIM_TEST_IMAGE_PATH_EXTRA path of another vm image to use, the image is created if not exist"
+}
+
+#detect if is called with a source to use the 'exit'/'return' command for exiting
+[[ ${BASH_SOURCE[0]} != $0 ]] && echo "Do not execute this script as SOURCE" >&2 && return 1
+
+#check correct arguments
+force=n
+same_vlan=n
+create=n
+for param in $*
+do
+ if [[ $param == -h ]] || [[ $param == --help ]]
+ then
+ usage
+ exit 0
+ elif [[ $param == -v ]] || [[ $param == --same-vlan ]]
+ then
+ same_vlan=y
+ elif [[ $param == -f ]] || [[ $param == --force ]]
+ then
+ force=y
+ elif [[ $param == -c ]] || [[ $param == --create ]]
+ then
+ create=y
+ else
+ echo "invalid argument '$param'?" && usage >&2 && exit 1
+ fi
+done
+
+#detect if environment variables are set
+fail=""
+[[ $create == n ]] && [[ -z $VIM_TEST_NETWORK_INTERNET ]] && echo "VIM_TEST_NETWORK_INTERNET not defined" >&2 && fail=1
+[[ $create == n ]] && [[ -z $VIM_TEST_IMAGE_PATH ]] && echo "VIM_TEST_IMAGE_PATH not defined" >&2 && fail=1
+[[ $create == n ]] && [[ -z $VIM_TEST_IMAGE_PATH_EXTRA ]] && echo "VIM_TEST_IMAGE_PATH_EXTRA not defined" >&2 && fail=1
+[[ -n $fail ]] && exit 1
+
+[[ $create == y ]] && [[ -z $VIM_TEST_IMAGE_PATH ]] && VIM_TEST_IMAGE_PATH="/test/path/of/image1"
+[[ $create == y ]] && [[ -z $VIM_TEST_IMAGE_PATH_EXTRA ]] && VIM_TEST_IMAGE_PATH_EXTRA="/test/path2/of/image2"
+TODELETE=""
+export _exit=delete_and_exit
+
+function delete_and_exit(){
+ echo
+ [[ $force != y ]] && read -e -p " Press enter to delete the deployed things " kk
+ echo
+ for f in $TODELETE
+ do
+ openvim ${f%%:*}-delete ${f##*:} -f
+ done
+ exit $1
+}
+
+
+function is_valid_uuid(){
+ echo "$1" | grep -q -E '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$' && return 0
+ return 1
+}
+
+function process_cmd(){
+ # test the result of the previos command, if fails execute the $_exit command
+ # params:
+ # uuid <variable> <result...> : test that the first word of <result> is a valid uuid, stored it at <variable>. Print uuid
+ # fail <reason> <result...> : test that the previous command has failed. If not print the <reason> why it needs to fail
+ # ok <result...> : test that the previos command has not failed. Print OK
+ cmd_result=$?
+ if [[ $1 == uuid ]]
+ then
+ [[ $cmd_result == 0 ]] || ! shift 2 || ! echo "FAIL: $*" >&2 || $_exit 1
+ is_valid_uuid $3 || ! shift 2 || ! echo "FAIL: $*" >&2 || $_exit 1
+ eval $2=$3
+ echo $3
+ elif [[ $1 == fail ]]
+ then
+ [[ $cmd_result != "0" ]] || ! echo "NOT FAIL: $2" >&2 || $_exit 1
+ echo "fail OK"
+ elif [[ $1 == ok ]]
+ then
+ [[ $cmd_result == 0 ]] || ! shift 1 || ! echo "FAIL: $*" >&2 || $_exit 1
+ echo OK
+ fi
+
+}
+
+function test_of_rules(){
+ #test the number of rules of a network, wait until 10 seconds
+ timeout_=10
+ while true
+ do #it can take some seconds to get it ready
+ result=`openvim openflow-net-list $1`
+ nb_rules=`echo $result | grep actions -o | wc -l`
+ [[ $nb_rules == $2 ]] && echo "OK" && break
+ [[ $timeout_ == 0 ]] && echo "FAIL $result" >&2 && $_exit 1
+ sleep 1
+ timeout_=$((timeout_ - 1))
+ done
+}
+
+echo " Test VIM with 3 VM deployments. It delete the created items at the end"
+echo " "
+[[ $force != y ]] && read -e -p "Press enter to continue, CTRL+C to abort " kk
+
+
+printf "%-50s" "1 get ${VIM_TEST_IMAGE_PATH##*/} image: "
+image1=`openvim image-list -F"path=${VIM_TEST_IMAGE_PATH}" | gawk '{print $1}'`
+if is_valid_uuid $image1
+then
+ echo $image1
+else
+ #create the image
+ echo not found
+ printf "%-50s" " b create ${VIM_TEST_IMAGE_PATH##*/} image: "
+ result=`openvim image-create --name=test-image1 --path=${VIM_TEST_IMAGE_PATH} --description=for-test`
+ process_cmd uuid image1 $result
+ TODELETE="image:$image1 $TODELETE"
+fi
+
+printf "%-50s" "2 get ${VIM_TEST_IMAGE_PATH_EXTRA##*/} image: "
+image2=`openvim image-list -F"path=${VIM_TEST_IMAGE_PATH_EXTRA}" | gawk '{print $1}'`
+if is_valid_uuid $image2
+then
+ echo $image2
+else
+ #create the image
+ echo not found
+ printf "%-50s" " b create ${VIM_TEST_IMAGE_PATH_EXTRA##*/} image: "
+ result=`openvim image-create --name=test-image1 --path=${VIM_TEST_IMAGE_PATH_EXTRA} --description=for-test`
+ process_cmd uuid image2 $result
+ TODELETE="image:$image2 $TODELETE"
+fi
+
+if [[ $create == y ]]
+then
+ printf "%-50s" "3 create management network: "
+ result=`openvim net-create "name: test_mgmt_net
+type: bridge_man"`
+ process_cmd uuid network_eth0 $result
+ TODELETE="net:$network_eth0 $TODELETE"
+else
+ printf "%-50s" "3 get ${VIM_TEST_NETWORK_INTERNET} network: "
+ result=`openvim net-list -F"name=$VIM_TEST_NETWORK_INTERNET"`
+ process_cmd uuid network_eth0 $result
+fi
+
+printf "%-50s" "4 insert flavor1: "
+result=`openvim flavor-create '
+---
+flavor:
+ name: flavor1
+ description: flavor to test openvim
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ paired-threads: 5
+ interfaces:
+ - name: xe0
+ dedicated: "yes"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:10.0"
+ #mac_address: "10:10:10:10:10:12"
+ - name: xe1
+ dedicated: "no"
+ bandwidth: "10 Gbps"
+ vpci: "0000:00:11.0"
+ mac_address: "10:10:10:10:10:13"
+'`
+process_cmd uuid flavor1 $result
+TODELETE="flavor:$flavor1 $TODELETE"
+
+printf "%-50s" "5 insert net_ptp: "
+result=`openvim net-create '
+---
+network:
+ name: test_net_ptp
+ type: ptp
+'`
+process_cmd uuid net_ptp $result
+TODELETE="net:$net_ptp $TODELETE"
+
+printf "%-50s" " b insert net_data: "
+result=`openvim net-create '
+---
+network:
+ name: test_net_data
+ type: data
+'`
+process_cmd uuid net_data $result
+TODELETE="net:$net_data $TODELETE"
+
+printf "%-50s" "6 insert net_bind network bound to net_data: "
+result=`openvim net-create 'name: test_net_binded
+type: data
+bind_net: test_net_data'`
+process_cmd uuid net_bind $result
+TODELETE="net:$net_bind $TODELETE"
+
+printf "%-50s" "7 insert bridge network net2: "
+result=`openvim net-create '
+---
+network:
+ name: test_bridge_net2
+ type: bridge_data'`
+process_cmd uuid network2 $result
+TODELETE="net:$network2 $TODELETE"
+
+printf "%-50s" "8 add VM1 dataplane not connected: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VM1
+ descrition: US or server with 1 SRIOV 1 PASSTHROUGH
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:10:10'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:10:11'
+"`
+process_cmd uuid server1 $result
+TODELETE="vm:$server1 $TODELETE"
+
+printf "%-50s" "9 add VM2 oversubscribe flavor: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VM2
+ descrition: US or server with direct network attach
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:11:10'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ mac_address: '10:10:10:10:11:11'
+ extended:
+ processor_ranking: 205
+ numas:
+ - memory: 8
+ threads: 10
+ interfaces:
+ - name: xe0
+ dedicated: 'yes:sriov'
+ bandwidth: '10 Gbps'
+ vpci: '0000:00:11.0'
+ mac_address: '10:10:10:10:11:12'
+ uuid: '$net_ptp'
+ devices:
+ - type: disk
+ imageRef: '$image2'
+"`
+process_cmd uuid server2 $result
+TODELETE="vm:$server2 $TODELETE"
+
+printf "%-50s" "10 test VM with repeated vpci: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VMfail
+ descrition: repeated mac address
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:10.0'
+ uuid: ${network_eth0}
+"`
+process_cmd fail "Duplicate vpci 0000:00:10.0" $result
+
+printf "%-50s" " b test VM with repeated mac address: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VMfail
+ descrition: repeated mac address
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:10:10'
+"`
+process_cmd fail "Duplicate mac 10:10:10:10:10:10" $result
+
+
+printf "%-50s" " c test VM with wrong iface name at networks: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VMfail
+ descrition: repeated mac address
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: missing
+ type: PF
+ uuid: '$net_ptp'
+"`
+process_cmd fail "wrong iface name at networks" $result
+
+
+printf "%-50s" " d test VM with wrong iface type at networks: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VMfail
+ descrition: repeated mac address
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: xe0
+ type: VF
+ uuid: '$net_ptp'
+"`
+process_cmd fail "wrong iface type at networks" $result
+
+
+printf "%-50s" "11 add VM3 dataplane connected: "
+result=`openvim vm-create "
+---
+server:
+ name: test_VM3
+ descrition: US or server with 2 dataplane connected
+ imageRef: '$image1'
+ flavorRef: '$flavor1'
+ networks:
+ - name: mgmt0
+ vpci: '0000:00:0a.0'
+ uuid: ${network_eth0}
+ mac_address: '10:10:10:10:12:10'
+ - name: eth0
+ vpci: '0000:00:0b.0'
+ uuid: '$network2'
+ type: virtual
+ mac_address: '10:10:10:10:12:11'
+ - name: xe0
+ type: PF
+ uuid: '$net_data'
+ - name: xe1
+ type: VF
+ uuid: '$net_ptp'
+ mac_address: '10:10:10:10:12:13'
+"`
+process_cmd uuid server3 $result
+TODELETE="vm:$server3 $TODELETE"
+
+printf "%-50s" "12 check 2 openflow rules for net_ptp: "
+test_of_rules $net_ptp 2
+
+printf "%-50s" "13 check net-down net_ptp: "
+result=`openvim net-down -f ${net_ptp}`
+process_cmd ok $result
+
+printf "%-50s" " b check 0 openflow rules for net_ptp: "
+test_of_rules $net_ptp 0
+
+printf "%-50s" " c check net-up net_ptp: "
+result=`openvim net-up -f ${net_ptp}`
+process_cmd ok $result
+
+printf "%-50s" " d check 2 openflow rules for net_ptp: "
+test_of_rules $net_ptp 2
+
+printf "%-50s" "14 check 0 openflow rules for net_data: "
+test_of_rules $net_data 0
+
+[[ $force != y ]] && read -e -p " Test control plane, and server2:xe0 to server3:xe1 connectivity. Press enter to continue " kk
+
+printf "%-50s" "15 get xe0 iface uuid from server1: "
+result=`openvim port-list -F"device_id=${server1}&name=xe0"`
+process_cmd uuid server1_xe0 $result
+
+printf "%-50s" " b get xe1 iface uuid from server1: "
+result=`openvim port-list -F"device_id=${server1}&name=xe1"`
+process_cmd uuid server1_xe1 $result
+
+printf "%-50s" " c get xe0 iface uuid from server3: "
+result=`openvim port-list -F"device_id=${server3}&name=xe0"`
+process_cmd uuid server3_xe0 $result
+
+printf "%-50s" " d get xe1 iface uuid from server3: "
+result=`openvim port-list -F"device_id=${server3}&name=xe1"`
+process_cmd uuid server3_xe1 $result
+
+printf "%-50s" " e get xe0 iface uuid from server3: "
+result=`openvim port-list -F"device_id=${server2}&name=xe0"`
+process_cmd uuid server2_xe0 $result
+
+printf "%-50s" "16 test ptp 3connex server1:xe0 -> net_ptp: "
+result=`openvim port-edit $server1_xe0 "network_id: $net_ptp" -f`
+process_cmd fail "Can not connect 3 interfaces to ptp network"
+
+printf "%-50s" "17 attach server1:xe0 to net_data: "
+result=`openvim port-edit $server1_xe0 "network_id: $net_data" -f`
+process_cmd ok $result
+
+printf "%-50s" "18 check 2 openflow rules for net_data: "
+test_of_rules $net_data 2
+
+[[ $force != y ]] && read -e -p " Test server1:xe0 to server3:xe0 connectivity. Press enter to continue " kk
+
+if [[ $same_vlan == n ]]
+then
+
+ printf "%-50s" "19 attach server1:xe1 to net-data: "
+ result=`openvim port-edit $server1_xe1 "network_id: $net_data" -f`
+ process_cmd ok $result
+
+ printf "%-50s" " b check 9 openflow rules for net_data: "
+ test_of_rules $net_data 9
+
+ [[ $force != y ]] && read -e -p " Test server1:xe0,server1:xe1,server3:xe0 connectivity. Press enter to continue " kk
+
+ printf "%-50s" " c re-attach server3:xe1 to net-data: "
+ result=`openvim port-edit $server3_xe1 "network_id: $net_data" -f`
+ process_cmd ok $result
+
+ printf "%-50s" " d check 16 openflow rules for net_data: "
+ test_of_rules $net_data 16
+
+ printf "%-50s" " e check 0 openflow rules for net_ptp: "
+ test_of_rules $net_ptp 0
+
+ [[ $force != y ]] && read -e -p " Test server1:xe0,server1:xe1,server3:xe0,server3:xe1 connectivity. Press enter to continue " kk
+
+ printf "%-50s" " f detach server1:xe1 from net-data: "
+ result=`openvim port-edit $server1_xe1 "network_id: null" -f `
+ process_cmd ok $result
+
+ printf "%-50s" " g detach server3:xe1 to net-data: "
+ result=`openvim port-edit $server3_xe1 "network_id: null" -f`
+ process_cmd ok $result
+
+ printf "%-50s" " h check 2 openflow rules for net_data: "
+ test_of_rules $net_data 2
+
+else
+ echo "19 skipping unrealizable test because --same_vlan option "
+fi
+
+printf "%-50s" "20 check 2 openflow rules for net_data: "
+test_of_rules $net_data 2
+
+printf "%-50s" " a attach server2:xe0 to net_bind: "
+result=`openvim port-edit $server2_xe0 "network_id: $net_bind" -f`
+process_cmd ok $result
+
+printf "%-50s" " b check 6 openflow rules for net_data: "
+ #type src_net src_port => dst_port dst_net
+ #unicast net_data server1:xe0 => server3:xe0 net_data
+ #unicast net_data server3:xe0 => server1:xe0 net_data
+ #unicast net_data server1:xe0 => server2:xe0 net_bind
+ #unicast net_data server3:xe0 => server2:xe0 net_bind
+ #broadcast net_data server1:xe0 => server3:xe0,server2:xe0 net_data,net_bind
+ #broadcast net_data server3:xe0 => server1:xe0,server2:xe0 net_data,net_bind
+test_of_rules $net_data 6
+
+
+printf "%-50s" " c check 3 openflow rules for net_bind: "
+ #type src_net src_port => dst_port dst_net
+ #unicast net_bind server2:xe0 => server1:xe0 net_data
+ #unicast net_bind server2:xe0 => server3:xe0 net_data
+ #broadcast net_bind server2:xe0 => server1:xe0,server3:xe0 net_data,net_data
+test_of_rules $net_bind 3
+
+printf "%-50s" " d attach server1:xe1 to net_bind: "
+result=`openvim port-edit $server1_xe1 "network_id: $net_bind" -f`
+process_cmd ok $result
+
+printf "%-50s" " e check 8 openflow rules for net_data: "
+ #type src_net src_port => dst_port dst_net
+ #unicast net_data server1:xe0 => server3:xe0 net_data
+ #unicast net_data server3:xe0 => server1:xe0 net_data
+ #unicast net_data server1:xe0 => server2:xe0 net_bind
+ #unicast net_data server1:xe0 => server1:xe1 net_bind
+ #unicast net_data server3:xe0 => server2:xe0 net_bind
+ #unicast net_data server3:xe0 => server1:xe1 net_bind
+ #broadcast net_data server1:xe0 => server3:xe0,server2:xe0,server1:xe1 net_data,net_bind,net_bind
+ #broadcast net_data server3:xe0 => server1:xe0,server2:xe0,server1:xe1 net_data,net_bind,net_bind
+test_of_rules $net_data 8
+
+
+printf "%-50s" " f check 8 openflow rules for net_bind: "
+test_of_rules $net_bind 8
+
+printf "%-50s" " d put net_data down: "
+result=`openvim net-down $net_data -f`
+process_cmd ok $result
+
+printf "%-50s" " e check 0 openflow rules for net_data: "
+test_of_rules $net_data 0
+
+printf "%-50s" " e check 2 openflow rules for net_bind: "
+test_of_rules $net_bind 2
+
+
+
+echo
+echo DONE
+
+$_exit 0
+
--- /dev/null
+# -*- coding: utf-8 -*-
+import code
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Implement the logic for obtaining compute nodes information
+Resource Availability Descriptor
+'''
+__author__="Pablo Montes"
+
+#TODO: remove warnings, remove unused things
+
+from definitionsClass import definitionsClass
+from auxiliary_functions import get_ssh_connection
+import libvirt
+from xml.etree import ElementTree
+import paramiko
+import re
+import yaml
+
+
+def getCredentials(creds, data):
+ """Used as a backup for libvirt.openAuth in order to provide password that came with data,
+ not used by the moment
+ """
+ print "RADclass:getCredentials", creds, data
+ for cred in creds:
+ print cred[1] + ": ",
+ if cred[0] == libvirt.VIR_CRED_AUTHNAME:
+ cred[4] = data
+ elif cred[0] == libvirt.VIR_CRED_PASSPHRASE:
+ cred[4] = data
+ else:
+ return -1
+ return 0
+
+class RADclass():
+ def __init__(self):
+ self.name = None
+ self.machine = None
+ self.user = None
+ self.password = None
+ self.nodes = dict() #Dictionary of nodes. Keys are the node id, values are Node() elements
+ self.nr_processors = None #Integer. Number of processors in the system
+ self.processor_family = None #If all nodes have the same value equal them, otherwise keep as None
+ self.processor_manufacturer = None #If all nodes have the same value equal them, otherwise keep as None
+ self.processor_version = None #If all nodes have the same value equal them, otherwise keep as None
+ self.processor_features = None #If all nodes have the same value equal them, otherwise keep as None
+ self.memory_type = None #If all nodes have the same value equal them, otherwise keep as None
+ self.memory_freq = None #If all nodes have the same value equal them, otherwise keep as None
+ self.memory_nr_channels = None #If all nodes have the same value equal them, otherwise keep as None
+ self.memory_size = None #Integer. Sum of the memory in all nodes
+ self.memory_hugepage_sz = None
+ self.hypervisor = Hypervisor() #Hypervisor information
+ self.os = OpSys() #Operating system information
+ self.ports_list = list() #List containing all network ports in the node. This is used to avoid having defined multiple times the same port in the system
+
+
+ def obtain_RAD(self, user, password, machine):
+ """This function obtains the RAD information from the remote server.
+ It uses both a ssh and a libvirt connection.
+ It is desirable in future versions get rid of the ssh connection, but currently
+ libvirt does not provide all the needed information.
+ Returns (True, Warning) in case of success and (False, <error>) in case of error"""
+ warning_text=""
+ try:
+ #Get virsh and ssh connection
+ (return_status, code) = get_ssh_connection(machine, user, password)
+ if not return_status:
+ print 'RADclass.obtain_RAD() error:', code
+ return (return_status, code)
+ ssh_conn = code
+
+ self.connection_IP = machine
+ #print "libvirt open pre"
+ virsh_conn=libvirt.open("qemu+ssh://"+user+'@'+machine+"/system")
+ #virsh_conn=libvirt.openAuth("qemu+ssh://"+user+'@'+machine+"/system",
+ # [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE, libvirt.VIR_CRED_USERNAME], getCredentials, password],
+ # 0)
+ #print "libvirt open after"
+
+ # #Set connection infomation
+ # (return_status, code) = self.set_connection_info(machine, user, password)
+ # if not return_status:
+ # return (return_status, 'Error in '+machine+': '+code)
+
+ #Set server name
+ machine_name = get_hostname(virsh_conn)
+ (return_status, code) = self.set_name(machine_name)
+ if not return_status:
+ return (return_status, 'Error at self.set_name in '+machine+': '+code)
+ warning_text += code
+
+ #Get the server processors information
+ processors = dict()
+ (return_status, code) = get_processor_information(ssh_conn, virsh_conn, processors)
+ if not return_status:
+ return (return_status, 'Error at get_processor_information in '+machine+': '+code)
+ warning_text += code
+
+ #Get the server memory information
+ memory_nodes = dict()
+ (return_status, code) = get_memory_information(ssh_conn, virsh_conn, memory_nodes)
+ if not return_status:
+ return (return_status, 'Error at get_memory_information in '+machine+': '+code)
+ warning_text += code
+
+ #Get nics information
+ nic_topology = dict()
+ # (return_status, code) = get_nic_information_old(ssh_conn, nic_topology)
+ (return_status, code) = get_nic_information(ssh_conn, virsh_conn, nic_topology)
+ if not return_status:
+ return (return_status, 'Error at get_nic_information in '+machine+': '+code)
+ warning_text += code
+
+ #Pack each processor, memory node and nics in a node element
+ #and add the node to the RAD element
+ for socket_id, processor in processors.iteritems():
+ node = Node()
+ if not socket_id in nic_topology:
+ nic_topology[socket_id] = list()
+
+ (return_status, code) = node.set(processor, memory_nodes[socket_id], nic_topology[socket_id])
+ # else:
+ # (return_status, code) = node.set(processor, memory_nodes[socket_id])
+ if not return_status:
+ return (return_status, 'Error at node.set in '+machine+': '+code)
+ warning_text += code
+ (return_status, code) = self.insert_node(node)
+ if not return_status:
+ return (return_status, 'Error at self.insert_node in '+machine+': '+code)
+ if code not in warning_text:
+ warning_text += code
+
+ #Fill os data
+ os = OpSys()
+ (return_status, code) = get_os_information(ssh_conn, os)
+ if not return_status:
+ return (return_status, 'Error at get_os_information in '+machine+': '+code)
+ warning_text += code
+ (return_status, code) = self.set_os(os)
+ if not return_status:
+ return (return_status, 'Error at self.set_os in '+machine+': '+code)
+ warning_text += code
+
+ #Fill hypervisor data
+ hypervisor = Hypervisor()
+ (return_status, code) = get_hypervisor_information(virsh_conn, hypervisor)
+ if not return_status:
+ return (return_status, 'Error at get_hypervisor_information in '+machine+': '+code)
+ warning_text += code
+ (return_status, code) = self.set_hypervisor(hypervisor)
+ if not return_status:
+ return (return_status, 'Error at self.set_hypervisor in '+machine+': '+code)
+ warning_text += code
+ ssh_conn.close()
+
+ return (True, warning_text)
+ except libvirt.libvirtError, e:
+ text = e.get_error_message()
+ print 'RADclass.obtain_RAD() exception:', text
+ return (False, text)
+ except paramiko.ssh_exception.SSHException, e:
+ text = e.args[0]
+ print "obtain_RAD ssh Exception:", text
+ return False, text
+
+ def set_name(self,name):
+ """Sets the machine name.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(name,str):
+ return (False, 'The variable \'name\' must be text')
+ self.name = name
+ return (True, "")
+
+ def set_connection_info(self, machine, user, password):
+ """Sets the connection information.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(machine,str):
+ return (False, 'The variable \'machine\' must be text')
+ if not isinstance(user,str):
+ return (False, 'The variable \'user\' must be text')
+# if not isinstance(password,str):
+# return (False, 'The variable \'password\' must be text')
+ (self.machine, self.user, self.password) = (machine, user, password)
+ return (True, "")
+
+ def insert_node(self,node):
+ """Inserts a new node and updates class variables.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(node,Node):
+ return (False, 'The variable \'node\' must be a Node element')
+
+ if node.id_ in self.nodes:
+ return (False, 'The node is already present in the nodes list.')
+
+ #Check if network ports have not been inserted previously as part of another node
+ for port_key in node.ports_list:
+ if port_key in self.ports_list:
+ return (False, 'Network port '+port_key+' defined multiple times in the system')
+ self.ports_list.append(port_key)
+
+ #Insert the new node
+ self.nodes[node.id_] = node
+
+ #update variables
+ self.update_variables()
+
+ return (True, "")
+
+ def update_variables(self):
+ """Updates class variables.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ warning_text=""
+ #The number of processors and nodes is the same
+ self.nr_processors = len(self.nodes)
+
+ #If all processors are the same get the values. Otherwise keep them as none
+ prev_processor_family = prev_processor_manufacturer = prev_processor_version = prev_processor_features = None
+ different_processor_family = different_processor_manufacturer = different_processor_version = different_processor_features = False
+ for node in self.nodes.itervalues():
+ (self.processor_family, self.processor_manufacturer, self.processor_version, self.processor_features) = node.get_processor_info()
+ if prev_processor_family != None and self.processor_family != prev_processor_family:
+ different_processor_family = True
+ if prev_processor_manufacturer != None and self.processor_manufacturer != prev_processor_manufacturer:
+ different_processor_manufacturer = True
+ if prev_processor_version != None and self.processor_version != prev_processor_version:
+ different_processor_version = True
+ if prev_processor_features != None and self.processor_features != prev_processor_features:
+ different_processor_features = True
+ (prev_processor_family, prev_processor_manufacturer, prev_processor_version, prev_processor_features) = (self.processor_family, self.processor_manufacturer, self.processor_version, self.processor_features)
+
+ if different_processor_family:
+ self.processor_family = None
+ if different_processor_features:
+ self.processor_features = None
+ if different_processor_manufacturer:
+ self.processor_manufacturer = None
+ if different_processor_version:
+ self.processor_version = None
+
+ #If all memory nodes are the same get the values. Otherwise keep them as none
+ #Sum the total memory
+ self.memory_size = 0
+ different_memory_freq = different_memory_nr_channels = different_memory_type = different_memory_hugepage_sz = False
+ prev_memory_freq = prev_memory_nr_channels = prev_memory_type = prev_memory_hugepage_sz = None
+ for node in self.nodes.itervalues():
+ (self.memory_freq, self.memory_nr_channels, self.memory_type, memory_size, self.memory_hugepage_sz) = node.get_memory_info()
+ self.memory_size += memory_size
+ if prev_memory_freq != None and self.memory_freq != prev_memory_freq:
+ different_memory_freq = True
+ if prev_memory_nr_channels != None and self.memory_nr_channels != prev_memory_nr_channels:
+ different_memory_nr_channels = True
+ if prev_memory_type != None and self.memory_type != prev_memory_type:
+ different_memory_type = True
+ if prev_memory_hugepage_sz != None and self.memory_hugepage_sz != prev_memory_hugepage_sz:
+ different_memory_hugepage_sz = True
+ (prev_memory_freq, prev_memory_nr_channels, prev_memory_type, prev_memory_hugepage_sz) = (self.memory_freq, self.memory_nr_channels, self.memory_type, self.memory_hugepage_sz)
+
+ if different_memory_freq:
+ self.memory_freq = None
+ if different_memory_nr_channels:
+ self.memory_nr_channels = None
+ if different_memory_type:
+ self.memory_type = None
+ if different_memory_hugepage_sz:
+ warning_text += 'Detected different hugepages size in different sockets\n'
+
+ return (True, warning_text)
+
+ def set_hypervisor(self,hypervisor):
+ """Sets the hypervisor.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(hypervisor,Hypervisor):
+ return (False, 'The variable \'hypervisor\' must be of class Hypervisor')
+
+ self.hypervisor.assign(hypervisor)
+ return (True, "")
+
+ def set_os(self,os):
+ """Sets the operating system.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(os,OpSys):
+ return (False, 'The variable \'os\' must be of class OpSys')
+
+ self.os.assign(os)
+ return (True, "")
+
+ def to_text(self):
+ text= 'name: '+str(self.name)+'\n'
+ text+= 'processor:\n'
+ text+= ' nr_processors: '+str(self.nr_processors)+'\n'
+ text+= ' family: '+str(self.processor_family)+'\n'
+ text+= ' manufacturer: '+str(self.processor_manufacturer)+'\n'
+ text+= ' version: '+str(self.processor_version)+'\n'
+ text+= ' features: '+str(self.processor_features)+'\n'
+ text+= 'memory:\n'
+ text+= ' type: '+str(self.memory_type)+'\n'
+ text+= ' freq: '+str(self.memory_freq)+'\n'
+ text+= ' nr_channels: '+str(self.memory_nr_channels)+'\n'
+ text+= ' size: '+str(self.memory_size)+'\n'
+ text+= 'hypervisor:\n'
+ text+= self.hypervisor.to_text()
+ text+= 'os:\n'
+ text+= self.os.to_text()
+ text+= 'resource topology:\n'
+ text+= ' nr_nodes: '+ str(len(self.nodes))+'\n'
+ text+= ' nodes:\n'
+ for node_k, node_v in self.nodes.iteritems():
+ text+= ' node'+str(node_k)+':\n'
+ text+= node_v.to_text()
+ return text
+
+ def to_yaml(self):
+ return yaml.load(self.to_text())
+
+class Node():
+ def __init__(self):
+ self.id_ = None #Integer. Node id. Unique in the system
+ self.processor = ProcessorNode() #Information about the processor in the node
+ self.memory = MemoryNode() #Information about the memory in the node
+ self.nic_list = list() #List of Nic() containing information about the nics associated to the node
+ self.ports_list = list() #List containing all network ports in the node. This is used to avoid having defined multiple times the same port in the system
+
+ def get_processor_info(self):
+ """Gets the processor information. Returns (processor_family, processor_manufacturer, processor_version, processor_features)"""
+ return self.processor.get_info()
+
+ def get_memory_info(self):
+ """Gets the memory information. Returns (memory_freq, memory_nr_channels, memory_type, memory_size)"""
+ return self.memory.get_info()
+
+# def set(self, *args):
+# """Sets the node information. Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+# if len(args)==2:
+# processor = args[0]
+# memory = args[1]
+# nics = False
+# elif len(args)==3:
+# processor = args[0]
+# memory = args[1]
+# nic_list = args[2]
+# nics = True
+# else:
+# return (False, 'Wrong number of elements calling Node().set()')
+
+ def set(self, processor, memory, nic_list):
+ (status, return_code) = self.processor.assign(processor)
+ if not status:
+ return (status, return_code)
+
+ self.id_ = processor.id_
+
+ (status, return_code) = self.memory.assign(memory)
+ if not status:
+ return (status, return_code)
+
+# if nics:
+ for nic in nic_list:
+ if not isinstance(nic,Nic):
+ return (False, 'The nics must be of type Nic')
+ self.nic_list.append(nic)
+ for port_key in nic.ports.iterkeys():
+ if port_key in self.ports_list:
+ return (False, 'Network port '+port_key+'defined multiple times in the same node')
+ self.ports_list.append(port_key)
+
+ return (True,"")
+
+ def assign(self, node):
+ """Sets the node information.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ warning_text=""
+ processor = node.processor
+ memory = node.memory
+ nic_list = node.nic_list
+ (status, return_code) = self.processor.assign(processor)
+ if not status:
+ return (status, return_code)
+
+ self.id_ = processor.id_
+
+ (status, return_code) = self.memory.assign(memory)
+ if not status:
+ return (status, return_code)
+ warning_text += code
+
+ for nic in nic_list:
+ if not isinstance(nic,Nic):
+ return (False, 'The nics must be of type Nic')
+ self.nic_list.append(nic)
+ for port_key in nic.ports.iterkeys():
+ if port_key in self.ports_list:
+ return (False, 'Network port '+port_key+'defined multiple times in the same node')
+ self.ports_list.append(port_key)
+
+ return (True,warning_text)
+
+ def to_text(self):
+ text= ' id: '+str(self.id_)+'\n'
+ text+= ' cpu:\n'
+ text += self.processor.to_text()
+ text+= ' memory:\n'
+ text += self.memory.to_text()
+ if len(self.nic_list) > 0:
+ text+= ' nics:\n'
+ nic_index = 0
+ for nic in self.nic_list:
+ text+= ' nic '+str(nic_index)+':\n'
+ text += nic.to_text()
+ nic_index += 1
+ return text
+
+class ProcessorNode():
+ #Definition of the possible values of processor variables
+ possible_features = definitionsClass.processor_possible_features
+ possible_manufacturers = definitionsClass.processor_possible_manufacturers
+ possible_families = definitionsClass.processor_possible_families
+ possible_versions = definitionsClass.processor_possible_versions
+
+ def __init__(self):
+ self.id_ = None #Integer. Numeric identifier of the socket
+ self.family = None #Text. Family name of the processor
+ self.manufacturer = None #Text. Manufacturer of the processor
+ self.version = None #Text. Model version of the processor
+ self.features = list() #list. List of features offered by the processor
+ self.cores = list() #list. List of cores in the processor. In case of hyperthreading the coupled cores are expressed as [a,b]
+ self.eligible_cores = list()#list. List of cores that can be used
+ #self.decicated_cores
+ #self.shared_cores -> this should also contain information to know if cores are being used
+
+ def assign(self, processor):
+ """Sets the processor information.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(processor,ProcessorNode):
+ return (False, 'The variable \'processor\' must be of class ProcessorNode')
+
+ self.id_ = processor.id_
+ self.family = processor.family
+ self.manufacturer = processor.manufacturer
+ self.version = processor.version
+ self.features = processor.features
+ self.cores = processor.cores
+ self.eligible_cores = processor.eligible_cores
+
+ return (True, "")
+
+ def set(self, id_, family, manufacturer, version, features, cores):
+ """Sets the processor information.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ warning_text = ""
+
+ if not isinstance(id_,int):
+ return (False, 'The processor id_ must be of type int')
+ if not isinstance(family,str):
+ return (False, 'The processor family must be of type str')
+ if not isinstance(manufacturer,str):
+ return (False, 'The processor manufacturer must be of type str')
+ if not isinstance(version,str):
+ return (False, 'The processor version must be of type str')
+ if not isinstance(features,list):
+ return (False, 'The processor features must be of type list')
+ if not isinstance(cores,list):
+ return (False, 'The processor cores must be of type list')
+
+ (self.id_, self.family, self.manufacturer, self.version) = (id_, family, manufacturer, version)
+
+ if not manufacturer in self.possible_manufacturers:
+ warning_text += "processor manufacturer '%s' not among: %s\n" %(manufacturer, str(self.possible_manufacturers))
+ if not family in self.possible_families:
+ warning_text += "family '%s' not among: %s\n" % (family, str(self.possible_families))
+# if not version in self.possible_versions:
+# warning_text += 'The version %s is not one of these: %s\n' % (version, str(self.possible_versions))
+
+ for feature in features:
+ if not feature in self.possible_features:
+ warning_text += "processor feature '%s' not among: %s\n" % (feature, str(self.possible_versions))
+ self.features.append(feature)
+
+ for iterator in sorted(cores):
+ if not isinstance(iterator,list) or not all(isinstance(x, int) for x in iterator):
+ return (False, 'The cores list must be in the form of [[a,b],[c,d],...] where a,b,c,d are of type int')
+ self.cores.append(iterator)
+
+ self.set_eligible_cores()
+
+ return (True,warning_text)
+
+ def set_eligible_cores(self):
+ """Set the default eligible cores, this is all cores non used by the host operating system"""
+ not_first = False
+ for iterator in self.cores:
+ if not_first:
+ self.eligible_cores.append(iterator)
+ else:
+ not_first = True
+ return
+
+ def get_info(self):
+ """Returns processor parameters (self.family, self.manufacturer, self.version, self.features)"""
+ return (self.family, self.manufacturer, self.version, self.features)
+
+ def to_text(self):
+ text= ' id: '+str(self.id_)+'\n'
+ text+= ' family: '+self.family+'\n'
+ text+= ' manufacturer: '+self.manufacturer+'\n'
+ text+= ' version: '+self.version+'\n'
+ text+= ' features: '+str(self.features)+'\n'
+ text+= ' cores: '+str(self.cores)+'\n'
+ text+= ' eligible_cores: '+str(self.eligible_cores)+'\n'
+ return text
+
+class MemoryNode():
+ def __init__(self):
+ self.modules = list() #List of MemoryModule(). List of all modules installed in the node
+ self.nr_channels = None #Integer. Number of modules installed in the node
+ self.node_size = None #Integer. Total size in KiB of memory installed in the node
+ self.eligible_memory = None #Integer. Size in KiB of eligible memory in the node
+ self.hugepage_sz = None #Integer. Size in KiB of hugepages
+ self.hugepage_nr = None #Integer. Number of hugepages allocated in the module
+ self.eligible_hugepage_nr = None #Integer. Number of eligible hugepages in the node
+ self.type_ = None #Text. Type of memory modules. If modules have a different value keep it as None
+ self.freq = None #Integer. Frequency of the modules in MHz. If modules have a different value keep it as None
+ self.module_size = None #Integer. Size of the modules in KiB. If modules have a different value keep it as None
+ self.form_factor = None #Text. Form factor of the modules. If modules have a different value keep it as None
+
+ def assign(self, memory_node):
+ return self.set(memory_node.modules, memory_node.hugepage_sz, memory_node.hugepage_nr)
+
+ def set(self, modules, hugepage_sz, hugepage_nr):
+ """Set the memory node information. hugepage_sz must be expressed in KiB.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(modules, list):
+ return (False, 'The modules must be a list of elements of class MemoryModule')
+ if not isinstance(hugepage_sz,int):
+ return (False, 'The hugepage_sz variable must be an int expressing the size in KiB')
+ if not isinstance(hugepage_nr,int):
+ return (False, 'The hugepage_nr variable must be of type int')
+
+ (self.hugepage_sz, self.hugepage_nr) = (hugepage_sz, hugepage_nr)
+ self.node_size = self.nr_channels = 0
+
+ different_type = different_freq = different_module_size = different_form_factor = False
+ prev_type = prev_freq = prev_module_size = prev_form_factor = None
+ for iterator in modules:
+ if not isinstance(iterator,MemoryModule):
+ return (False, 'The modules must be a list of elements of class MemoryModule')
+ self.modules.append(iterator)
+ (self.type_, self.freq, self.module_size, self.form_factor) = (iterator.type_, iterator.freq, iterator.size, iterator.form_factor)
+ self.node_size += self.module_size
+ self.nr_channels += 1
+ if prev_type != None and prev_type != self.type_:
+ different_type = True
+ if prev_freq != None and prev_freq != self.freq:
+ different_freq = True
+ if prev_module_size != None and prev_module_size != self.module_size:
+ different_module_size = True
+ if prev_form_factor != None and prev_form_factor != self.form_factor:
+ different_form_factor = True
+ (prev_type, prev_freq, prev_module_size, prev_form_factor) = (self.type_, self.freq, self.module_size, self.form_factor)
+
+ if different_type:
+ self.type_ = None
+ if different_freq:
+ self.freq = None
+ if different_module_size:
+ self.module_size = None
+ if different_form_factor:
+ self.form_factor = None
+
+ (return_value, error_code) = self.set_eligible_memory()
+ if not return_value:
+ return (return_value, error_code)
+
+ return (True, "")
+
+ def set_eligible_memory(self):
+ """Sets the default eligible_memory and eligible_hugepage_nr. This is all memory but 2GiB and all hugepages"""
+ self.eligible_memory = self.node_size - 2*1024*1024
+ if self.eligible_memory < 0:
+ return (False, "There is less than 2GiB of memory in the module")
+
+ self.eligible_hugepage_nr = self.hugepage_nr
+ return (True,"")
+
+ def get_info(self):
+ """Return memory information (self.freq, self.nr_channels, self.type_, self.node_size)"""
+ return (self.freq, self.nr_channels, self.type_, self.node_size, self.hugepage_sz)
+
+ def to_text(self):
+ text= ' node_size: '+str(self.node_size)+'\n'
+ text+= ' nr_channels: '+str(self.nr_channels)+'\n'
+ text+= ' eligible_memory: '+str(self.eligible_memory)+'\n'
+ text+= ' hugepage_sz: '+str(self.hugepage_sz)+'\n'
+ text+= ' hugepage_nr: '+str(self.hugepage_nr)+'\n'
+ text+= ' eligible_hugepage_nr: '+str(self.eligible_hugepage_nr)+'\n'
+ text+= ' type: '+self.type_+'\n'
+ text+= ' freq: '+str(self.freq)+'\n'
+ text+= ' module_size: '+str(self.module_size)+'\n'
+ text+= ' form_factor: '+self.form_factor+'\n'
+ text+= ' modules details:\n'
+ for module in self.modules:
+ text += module.to_text()
+ return text
+
+class MemoryModule():
+ #Definition of the possible values of module variables
+ possible_types = definitionsClass.memory_possible_types
+ possible_form_factors = definitionsClass.memory_possible_form_factors
+
+ def __init__(self):
+ self.locator = None #Text. Name of the memory module
+ self.type_ = None #Text. Type of memory module
+ self.freq = None #Integer. Frequency of the module in MHz
+ self.size = None #Integer. Size of the module in KiB
+ self.form_factor = None #Text. Form factor of the module
+
+ def set(self, locator, type_, freq, size, form_factor):
+ """Sets the memory module information.
+ Frequency must be expressed in MHz and size in KiB.
+ Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ warning_text=""
+ if not isinstance(locator, str):
+ return (False, "The type of the variable locator must be str")
+ if not isinstance(type_, str):
+ return (False, "The type of the variable type_ must be str")
+ if not isinstance(form_factor, str):
+ return (False, "The type of the variable form_factor must be str")
+ if not isinstance(freq, int):
+ return (False, "The type of the variable freq must be int")
+ if not isinstance(size, int):
+ return (False, "The type of the variable size must be int")
+
+ if not form_factor in self.possible_form_factors:
+ warning_text += "memory form_factor '%s' not among: %s\n" %(form_factor, str(self.possible_form_factors))
+ if not type_ in self.possible_types:
+ warning_text += "memory type '%s' not among: %s\n" %(type_, str(self.possible_types))
+
+ (self.locator, self.type_, self.freq, self.size, self.form_factor) = (locator, type_, freq, size, form_factor)
+ return (True, warning_text)
+
+ def to_text(self):
+ text= ' '+self.locator+':\n'
+ text+= ' type: '+self.type_+'\n'
+ text+= ' freq: '+str(self.freq)+'\n'
+ text+= ' size: '+str(self.size)+'\n'
+ text+= ' form factor: '+self.form_factor+'\n'
+ return text
+
+class Nic():
+ def __init__(self):
+ self.model = None #Text. Model of the nic
+ self.ports = dict() #Dictionary of ports. Keys are the port name, value are Port() elements
+
+ def set_model(self, model):
+ """Sets the model of the nic. Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(model,str):
+ return (False, 'The \'model\' must be of type str')
+
+ self.model = model
+ return (True, "")
+
+ def add_port(self, port):
+ """Adds a port to the nic. Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+ if not isinstance(port,Port):
+ return (False, 'The \'port\' must be of class Port')
+
+# port_id = str(port.pci_device_id[0])+':'+str(port.pci_device_id[1])+':'+str(port.pci_device_id[2])+'.'+str(port.pci_device_id[3])
+#CHANGED
+# port_id = port.name
+ port_id = port.pci_device_id
+#CHANGED END
+ if port_id in self.ports:
+ return (False, 'The \'port\' '+port.pci_device_id+' is duplicated in the nic')
+# return (False, 'The \'port\' is duplicated in the nic')
+
+ self.ports[port_id] = port
+ return (True, "")
+
+ def to_text(self):
+ text= ' model: '+ str(self.model)+'\n'
+ text+= ' ports: '+'\n'
+ for key,port in self.ports.iteritems():
+ text+= ' "'+key+'":'+'\n'
+ text += port.to_text()
+ return text
+
+class Port():
+ def __init__(self):
+ self.name = None #Text. Port name
+ self.virtual = None #Boolean. States if the port is a virtual function
+ self.enabled = None #Boolean. States if the port is enabled
+ self.eligible = None #Boolean. States if the port is eligible
+ self.speed = None #Integer. Indicates the speed in Mbps
+ self.available_bw = None #Integer. BW in Mbps that is available.
+ self.mac = None #list. Indicates the mac address of the port as a list in format ['XX','XX','XX','XX','XX','XX']
+ self.pci_device_id_split = None #list. Indicates the pci address of the port as a list in format ['XXXX','XX','XX','X']
+ self.pci_device_id = None
+ self.PF_pci_device_id = None
+
+# def set(self, name, virtual, enabled, speed, mac, pci_device_id, pci_device_id_split):
+# """Sets the port information. The variable speed indicates the speed in Mbps. Returns (True,Warning) in case of success and ('False',<error description>) in case of error"""
+# if not isinstance(name,str):
+# return (False, 'The variable \'name\' must be of type str')
+# if not isinstance(virtual,bool):
+# return (False, 'The variable \'virtual\' must be of type bool')
+# if not isinstance(enabled,bool):
+# return (False, 'The variable \'enabled\' must be of type bool')
+# if not isinstance(enabled,bool):
+# return (speed, 'The variable \'speed\' must be of type int')
+# if not isinstance(mac, list) and not isinstance(mac,NoneType):
+# return (False, 'The variable \'enabled\' must be of type list indicating the mac address in format [\'XXXX\',\'XX\',\'XX\',\'X\'] or NoneType')
+# if not isinstance(pci_device_id_split, list) or len(pci_device_id_split) != 4:
+# return (False, 'The variable \'pci_device_id_split\' must be of type list, indicating the pci address in format [\'XX\',\'XX\',\'XX\',\'XX\',\'XX\',\'XX\']')
+#
+# expected_len = [4,2,2,1]
+# index = 0
+# for iterator in pci_device_id_split:
+# if not isinstance(iterator,str) or not iterator.isdigit() or len(iterator) != expected_len[index]:
+# return (False, 'The variable \'pci_device_id_split\' must be of type list, indicating the pci address in format [\'XX\',\'XX\',\'XX\',\'XX\',\'XX\',\'XX\']')
+# index += 1
+#
+# if not isinstance(mac,NoneType):
+# for iterator in mac:
+# if not isinstance(iterator,str) or not iterator.isalnum() or len(iterator) != 2:
+# return (False, 'The variable \'enabled\' must be of type list indicating the mac address in format [\'XXXX\',\'XX\',\'XX\',\'X\'] or NoneType')
+#
+# #By default only virtual ports are eligible
+# # (self.name, self.virtual, self.enabled, self.eligible, self.available_bw, self.speed, self.mac, self.pci_device_id, self.pci_device_id_split) = (name, virtual, enabled, virtual, speed, speed, mac, pci_device_id, pci_device_id_split)
+# (self.name, self.virtual, self.enabled, self.eligible, self.available_bw, self.mac, self.pci_device_id, self.pci_device_id_split) = (name, virtual, enabled, virtual, speed, mac, pci_device_id, pci_device_id_split)
+
+ def to_text(self):
+ text= ' pci: "'+ str(self.pci_device_id)+'"\n'
+ text+= ' virtual: '+ str(self.virtual)+'\n'
+ if self.virtual:
+ text+= ' PF_pci_id: "'+self.PF_pci_device_id+'"\n'
+ text+= ' eligible: '+ str(self.eligible)+'\n'
+ text+= ' enabled: '+str(self.enabled)+'\n'
+ text+= ' speed: '+ str(self.speed)+'\n'
+ text+= ' available bw: '+ str(self.available_bw)+'\n'
+ text+= ' mac: '+ str(self.mac)+'\n'
+ text+= ' source_name: '+ str(self.name)+'\n'
+ return text
+
+class Hypervisor():
+ #Definition of the possible values of hypervisor variables
+ possible_types = definitionsClass.hypervisor_possible_types
+ possible_domain_types = definitionsClass.hypervisor_possible_domain_types
+
+ def __init__(self):
+ self.type_ = None #Text. Hypervisor type_
+ self.version = None #int. Hypervisor version
+ self.lib_version = None #int. Libvirt version used to compile hypervisor
+ self.domains = list() #list. List of all the available domains
+
+ def set(self, hypervisor, version, lib_version, domains):
+ warning_text=""
+ if not isinstance(hypervisor,str):
+ return (False, 'The variable type_ must be of type str')
+ if not isinstance(version,int):
+ return (False, 'The variable version must be of type int')
+ if not isinstance(lib_version,int):
+ return (False, 'The library version must be of type int')
+ if not isinstance(domains,list):
+ return (False, 'Domains must be a list of the possible domains as str')
+
+ if not hypervisor in self.possible_types:
+ warning_text += "Hyperpivor '%s' not among: %s\n" % (hypervisor, str(self.possible_types))
+
+ valid_domain_found = False
+ for domain in domains:
+ if not isinstance(domain,str):
+ return (False, 'Domains must be a list of the possible domains as str')
+ if domain in self.possible_domain_types:
+ valid_domain_found = True
+ self.domains.append(domain)
+
+ if not valid_domain_found:
+ warning_text += 'No valid domain found among: %s\n' % str(self.possible_domain_types)
+
+
+ (self.version, self.lib_version, self.type_) = (version, lib_version, hypervisor)
+ return (True, warning_text)
+
+ def assign(self, hypervisor):
+ (self.version, self.lib_version, self.type_) = (hypervisor.version, hypervisor.lib_version, hypervisor.type_)
+ for domain in hypervisor.domains:
+ self.domains.append(domain)
+ return
+
+ def to_text(self):
+ text= ' type: '+self.type_+'\n'
+ text+= ' version: '+str(self.version)+'\n'
+ text+= ' libvirt version: '+ str(self.lib_version)+'\n'
+ text+= ' domains: '+str(self.domains)+'\n'
+ return text
+
+class OpSys():
+ #Definition of the possible values of os variables
+ possible_id = definitionsClass.os_possible_id
+ possible_types = definitionsClass.os_possible_types
+ possible_architectures = definitionsClass.os_possible_architectures
+
+ def __init__(self):
+ self.id_ = None #Text. Identifier of the OS. Formed by <Distibutor ID>-<Release>-<Codename>. In linux this can be obtained using lsb_release -a
+ self.type_ = None #Text. Type of operating system
+ self.bit_architecture = None #Integer. Architecture
+
+ def set(self, id_, type_, bit_architecture):
+ warning_text=""
+ if not isinstance(type_,str):
+ return (False, 'The variable type_ must be of type str')
+ if not isinstance(id_,str):
+ return (False, 'The variable id_ must be of type str')
+ if not isinstance(bit_architecture,str):
+ return (False, 'The variable bit_architecture must be of type str')
+
+ if not type_ in self.possible_types:
+ warning_text += "os type '%s' not among: %s\n" %(type_, str(self.possible_types))
+ if not id_ in self.possible_id:
+ warning_text += "os release '%s' not among: %s\n" %(id_, str(self.possible_id))
+ if not bit_architecture in self.possible_architectures:
+ warning_text += "os bit_architecture '%s' not among: %s\n" % (bit_architecture, str(self.possible_architectures))
+
+ (self.id_, self.type_, self.bit_architecture) = (id_, type_, bit_architecture)
+ return (True, warning_text)
+
+ def assign(self,os):
+ (self.id_, self.type_, self.bit_architecture) = (os.id_, os.type_, os.bit_architecture)
+ return
+
+ def to_text(self):
+ text= ' id: '+self.id_+'\n'
+ text+= ' type: '+self.type_+'\n'
+ text+= ' bit_architecture: '+self.bit_architecture+'\n'
+ return text
+
+def get_hostname(virsh_conn):
+ return virsh_conn.getHostname().rstrip('\n')
+
+def get_hugepage_size(ssh_conn):
+ command = 'sudo hugeadm --page-sizes'
+# command = 'hugeadm --page-sizes-all'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ mem=stdout.read()
+ if mem=="":
+ return 0
+ return int(mem)
+
+def get_hugepage_nr(ssh_conn,hugepage_sz, node_id):
+ command = 'cat /sys/devices/system/node/node'+str(node_id)+'/hugepages/hugepages-'+str(hugepage_sz/1024)+'kB/nr_hugepages'
+ (_, stdout, _) = ssh_conn.exec_command(command)
+ #print command,
+ #text = stdout.read()
+ #print "'"+text+"'"
+ #return int(text)
+
+ try:
+ value=int(stdout.read())
+ except:
+ value=0
+ return value
+
+def get_memory_information(ssh_conn, virsh_conn, memory_nodes):
+ warning_text=""
+ tree=ElementTree.fromstring(virsh_conn.getSysinfo(0))
+ memory_dict = dict()
+ node_id = 0 #TODO revise. Added for allowing VM as compute hosts
+ for target in tree.findall("memory_device"):
+ locator_f = size_f = freq_f = type_f = formfactor_f = False
+ locator_f = True #TODO revise. Added for allowing VM as compute hosts
+ module_form_factor = ""
+ for entry in target.findall("entry"):
+ if entry.get("name") == 'size':
+ size_f = True
+ size_split = entry.text.split(' ')
+ if size_split[1] == 'MB':
+ module_size = int(size_split[0]) * 1024 * 1024
+ elif size_split[1] == 'GB':
+ module_size = int(size_split[0]) * 1024 * 1024 * 1024
+ elif size_split[1] == 'KB':
+ module_size = int(size_split[0]) * 1024
+ else:
+ module_size = int(size_split[0])
+
+ elif entry.get("name") == 'speed':
+ freq_f = True
+ freq_split = entry.text.split(' ')
+ if freq_split[1] == 'MHz':
+ module_freq = int(freq_split[0]) * 1024 * 1024
+ elif freq_split[1] == 'GHz':
+ module_freq = int(freq_split[0]) * 1024 * 1024 * 1024
+ elif freq_split[1] == 'KHz':
+ module_freq = int(freq_split[0]) * 1024
+
+ elif entry.get("name") == 'type':
+ type_f = True
+ module_type = entry.text
+
+ elif entry.get("name") == 'form_factor':
+ formfactor_f = True
+ module_form_factor = entry.text
+ #TODO revise. Commented for allowing VM as compute hosts
+ # elif entry.get("name") == 'locator' and not locator_f:
+ # # other case, it is obtained by bank_locator that we give priority to
+ # locator = entry.text
+ # pos = locator.find(module_form_factor)
+ # if module_form_factor == locator[0:len(module_form_factor) ]:
+ # pos = len(module_form_factor) +1
+ # else:
+ # pos = 0
+ # if locator[pos] in "ABCDEFGH":
+ # locator_f = True
+ # node_id = ord(locator[pos])-ord('A')
+ # #print entry.text, node_id
+ #
+ # elif entry.get("name") == 'bank_locator':
+ # locator = entry.text
+ # pos = locator.find("NODE ")
+ # if pos >= 0 and len(locator)>pos+5:
+ # if locator[pos+5] in ("01234567"): #len("NODE ") is 5
+ # node_id = int(locator[pos+5])
+ # locator_f = True
+ #
+
+ #When all module fields have been found add a new module to the list
+ if locator_f and size_f and freq_f and type_f and formfactor_f:
+ #If the memory node has not yet been created create it
+ if node_id not in memory_dict:
+ memory_dict[node_id] = []
+
+ #Add a new module to the memory node
+ module = MemoryModule()
+ #TODO revise. Changed for allowing VM as compute hosts
+ (return_status, code) = module.set('NODE %d' % node_id, module_type, module_freq, module_size, module_form_factor)
+ #(return_status, code) = module.set(locator, module_type, module_freq, module_size, module_form_factor)
+ if not return_status:
+ return (return_status, code)
+ memory_dict[node_id].append(module)
+ if code not in warning_text:
+ warning_text += code
+ node_id += 1 #TODO revise. Added for allowing VM as compute hosts
+
+ #Fill memory nodes
+ #Hugepage size is constant for all nodes
+ hugepage_sz = get_hugepage_size(ssh_conn)
+ for node_id, modules in memory_dict.iteritems():
+ memory_node = MemoryNode()
+ memory_node.set(modules, hugepage_sz, get_hugepage_nr(ssh_conn,hugepage_sz, node_id))
+ memory_nodes[node_id] = memory_node
+
+ return (True, warning_text)
+
+def get_cpu_topology_ht(ssh_conn, topology):
+ command = 'cat /proc/cpuinfo'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ sockets = []
+ cores = []
+ core_map = {}
+ core_details = []
+ core_lines = {}
+ for line in stdout.readlines():
+ if len(line.strip()) != 0:
+ name, value = line.split(":", 1)
+ core_lines[name.strip()] = value.strip()
+ else:
+ core_details.append(core_lines)
+ core_lines = {}
+
+ for core in core_details:
+ for field in ["processor", "core id", "physical id"]:
+ if field not in core:
+ return(False,'Error getting '+field+' value from /proc/cpuinfo')
+ core[field] = int(core[field])
+
+ if core["core id"] not in cores:
+ cores.append(core["core id"])
+ if core["physical id"] not in sockets:
+ sockets.append(core["physical id"])
+ key = (core["physical id"], core["core id"])
+ if key not in core_map:
+ core_map[key] = []
+ core_map[key].append(core["processor"])
+
+ for s in sockets:
+ hyperthreaded_cores = list()
+ for c in cores:
+ hyperthreaded_cores.append(core_map[(s,c)])
+ topology[s] = hyperthreaded_cores
+
+ return (True, "")
+
+def get_processor_information(ssh_conn, vish_conn, processors):
+ warning_text=""
+ #Processor features are the same for all processors
+ #TODO (at least using virsh capabilities)nr_numa_nodes
+ capabilities = list()
+ tree=ElementTree.fromstring(vish_conn.getCapabilities())
+ for target in tree.findall("host/cpu/feature"):
+ if target.get("name") == 'pdpe1gb':
+ capabilities.append('lps')
+ elif target.get("name") == 'dca':
+ capabilities.append('dioc')
+ elif target.get("name") == 'vmx' or target.get("name") == 'svm':
+ capabilities.append('hwsv')
+ elif target.get("name") == 'ht':
+ capabilities.append('ht')
+
+ target = tree.find("host/cpu/arch")
+ if target.text == 'x86_64' or target.text == 'amd64':
+ capabilities.append('64b')
+
+ command = 'cat /proc/cpuinfo | grep flags'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ line = stdout.readline()
+ if 'ept' in line or 'npt' in line:
+ capabilities.append('tlbps')
+
+ #Find out if IOMMU is enabled
+ command = 'dmesg |grep -e Intel-IOMMU'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ if 'enabled' in stdout.read():
+ capabilities.append('iommu')
+
+ #Equivalent for AMD
+ command = 'dmesg |grep -e AMD-Vi'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ if len(stdout.read()) > 0:
+ capabilities.append('iommu')
+
+ #-----------------------------------------------------------
+ topology = dict()
+ #In case hyperthreading is active it is necessary to determine cpu topology using /proc/cpuinfo
+ if 'ht' in capabilities:
+ (return_status, code) = get_cpu_topology_ht(ssh_conn, topology)
+ if not return_status:
+ return (return_status, code)
+ warning_text += code
+
+ #Otherwise it is possible to do it using virsh capabilities
+ else:
+ for target in tree.findall("host/topology/cells/cell"):
+ socket_id = int(target.get("id"))
+ topology[socket_id] = list()
+ for cpu in target.findall("cpus/cpu"):
+ topology[socket_id].append(int(cpu.get("id")))
+
+ #-----------------------------------------------------------
+ #Create a dictionary with the information of all processors
+ #p_fam = p_man = p_ver = None
+ tree=ElementTree.fromstring(vish_conn.getSysinfo(0))
+ #print vish_conn.getSysinfo(0)
+ #return (False, 'forces error for debuging')
+ not_populated=False
+ socket_id = -1 #in case we can not determine the socket_id we assume incremental order, starting by 0
+ for target in tree.findall("processor"):
+ count = 0
+ socket_id += 1
+ #Get processor id, family, manufacturer and version
+ for entry in target.findall("entry"):
+ if entry.get("name") == "status":
+ if entry.text[0:11] == "Unpopulated":
+ not_populated=True
+ elif entry.get("name") == 'socket_destination':
+ socket_text = entry.text
+ if socket_text.startswith('CPU'):
+ socket_text = socket_text.strip('CPU')
+ socket_text = socket_text.strip() #removes trailing spaces
+ if socket_text.isdigit() and int(socket_text)<9 and int(socket_text)>0:
+ socket_id = int(socket_text) - 1
+
+ elif entry.get("name") == 'family':
+ family = entry.text
+ count += 1
+ elif entry.get("name") == 'manufacturer':
+ manufacturer = entry.text
+ count += 1
+ elif entry.get("name") == 'version':
+ version = entry.text.strip()
+ count += 1
+ if count != 3:
+ return (False, 'Error. Not all expected fields could be found in processor')
+
+ #Create and fill processor structure
+ if not_populated:
+ continue #avoid inconsistence of some machines where more socket detected than
+ processor = ProcessorNode()
+ (return_status, code) = processor.set(socket_id, family, manufacturer, version, capabilities, topology[socket_id])
+ if not return_status:
+ return (return_status, code)
+ if code not in warning_text:
+ warning_text += code
+
+ #Add processor to the processors dictionary
+ processors[socket_id] = processor
+
+ return (True, warning_text)
+
+def get_nic_information(ssh_conn, virsh_conn, nic_topology):
+ warning_text=""
+ #Get list of net devices
+ net_devices = virsh_conn.listDevices('net',0)
+ print virsh_conn.listDevices('net',0)
+ for device in net_devices:
+ try:
+ #Get the XML descriptor of the device:
+ net_XML = ElementTree.fromstring(virsh_conn.nodeDeviceLookupByName(device).XMLDesc(0))
+ #print "net_XML:" , net_XML
+ #obtain the parent
+ parent = net_XML.find('parent')
+ if parent == None:
+ print 'No parent was found in XML for device '+device
+ #Error. continue?-------------------------------------------------------------
+ continue
+ if parent.text == 'computer':
+ continue
+ if not parent.text.startswith('pci_'):
+ print device + ' parent is neither computer nor pci'
+ #Error. continue?-------------------------------------------------------------
+ continue
+ interface = net_XML.find('capability/interface').text
+ mac = net_XML.find('capability/address').text
+
+ #Get the pci XML
+ pci_XML = ElementTree.fromstring(virsh_conn.nodeDeviceLookupByName(parent.text).XMLDesc(0))
+ #print pci_XML
+ #Get pci
+ name = pci_XML.find('name').text.split('_')
+ pci = name[1]+':'+name[2]+':'+name[3]+'.'+name[4]
+
+ #If slot == 0 it is a PF, otherwise it is a VF
+ capability = pci_XML.find('capability')
+ if capability.get('type') != 'pci':
+ print device + 'Capability is not of type pci in '+parent.text
+ #Error. continue?-------------------------------------------------------------
+ continue
+ slot = capability.find('slot').text
+ bus = capability.find('bus').text
+ node_id = None
+ numa_ = capability.find('numa')
+ if numa_ != None:
+ node_id = numa_.get('node');
+ if node_id != None: node_id =int(node_id)
+ if slot == None or bus == None:
+ print device + 'Bus and slot not detected in '+parent.text
+ #Error. continue?-------------------------------------------------------------
+ continue
+ if slot != '0':
+ # print ElementTree.tostring(pci_XML)
+ virtual = True
+ capability_pf = capability.find('capability')
+ if capability_pf.get('type') != 'phys_function':
+ print 'physical_function not found in VF '+parent.text
+ #Error. continue?-------------------------------------------------------------
+ continue
+ PF_pci = capability_pf.find('address').attrib
+ PF_pci_text = PF_pci['domain'].split('x')[1]+':'+PF_pci['bus'].split('x')[1]+':'+PF_pci['slot'].split('x')[1]+'.'+PF_pci['function'].split('x')[1]
+
+ else:
+ virtual = False
+
+ #Obtain node for the port
+ if node_id == None:
+ node_id = int(bus)>>6
+ #print "node_id:", node_id
+
+ #Only for non virtual interfaces: Obtain speed and if link is detected (this must be done using ethtool)
+ if not virtual:
+ command = 'sudo ethtool '+interface+' | grep -e Speed -e "Link detected"'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error) >0:
+ print 'Error running '+command+'\n'+error
+ #Error. continue?-------------------------------------------------------------
+ continue
+ for line in stdout.readlines():
+ line = line.strip().rstrip('\n').split(': ')
+ if line[0] == 'Speed':
+ if line[1].endswith('Mb/s'):
+ speed = int(line[1].split('M')[0])*int(1e6)
+ elif line[1].endswith('Gb/s'):
+ speed = int(line[1].split('G')[0])*int(1e9)
+ elif line[1].endswith('Kb/s'):
+ speed = int(line[1].split('K')[0])*int(1e3)
+ else:
+ #the interface is listed but won't be used
+ speed = 0
+ elif line[0] == 'Link detected':
+ if line[1] == 'yes':
+ enabled = True
+ else:
+ enabled = False
+ else:
+ print 'Unnexpected output of command '+command+':'
+ print line
+ #Error. continue?-------------------------------------------------------------
+ continue
+
+ if not node_id in nic_topology:
+ nic_topology[node_id] = list()
+ #With this implementation we make the RAD with only one nic per node and this nic has all ports, TODO: change this by including parent information of PF
+ nic_topology[node_id].append(Nic())
+
+ #Load the appropriate nic
+ nic = nic_topology[node_id][0]
+
+ #Create a new port and fill it
+ port = Port()
+ port.name = interface
+ port.virtual = virtual
+ port.speed = speed
+ if virtual:
+ port.available_bw = 0
+ port.PF_pci_device_id = PF_pci_text
+ else:
+ port.available_bw = speed
+ if speed == 0:
+ port.enabled = False
+ else:
+ port.enabled = enabled
+
+ port.eligible = virtual #Only virtual ports are eligible
+ port.mac = mac
+ port.pci_device_id = pci
+ port.pci_device_id_split = name[1:]
+
+ #Save the port information
+ nic.add_port(port)
+ except Exception,e:
+ print 'Error: '+str(e)
+
+ #set in vitual ports if they are enabled
+ for nic in nic_topology.itervalues():
+ for port in nic[0].ports.itervalues():
+# print port.pci_device_id
+ if port.virtual:
+ enabled = nic[0].ports.get(port.PF_pci_device_id)
+ if enabled == None:
+ return(False, 'The PF '+port.PF_pci_device_id+' (VF '+port.pci_device_id+') is not present in ports dict')
+ #Only if the PF is enabled the VF can be enabled
+ if nic[0].ports[port.PF_pci_device_id].enabled:
+ port.enabled = True
+ else:
+ port.enabled = False
+
+ return (True, warning_text)
+
+def get_nic_information_old(ssh_conn, nic_topology):
+ command = 'lstopo-no-graphics --of xml'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ tree=ElementTree.fromstring(stdout.read())
+ for target in tree.findall("object/object"):
+ #Find numa nodes
+ if target.get("type") != "NUMANode":
+ continue
+ node_id = int(target.get("os_index"))
+ nic_topology[node_id] = list()
+
+ #find nics in numa node
+ for entry in target.findall("object/object"):
+ if entry.get("type") != 'Bridge':
+ continue
+ nic_name = entry.get("name")
+ model = None
+ nic = Nic()
+
+ #find ports in nic
+ for pcidev in entry.findall("object"):
+ if pcidev.get("type") != 'PCIDev':
+ continue
+ enabled = speed = mac = pci_busid = None
+ port = Port()
+ model = pcidev.get("name")
+ virtual = False
+ if 'Virtual' in model:
+ virtual = True
+ pci_busid = pcidev.get("pci_busid")
+ for osdev in pcidev.findall("object"):
+ name = osdev.get("name")
+ for info in osdev.findall("info"):
+ if info.get("name") != 'Address':
+ continue
+ mac = info.get("value")
+ #get the port speed and status
+ command = 'sudo ethtool '+name
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ return (False, 'Error obtaining '+name+' information: '+error)
+ ethtool = stdout.read()
+ if '10000baseT/Full' in ethtool:
+ speed = 10e9
+ elif '1000baseT/Full' in ethtool:
+ speed = 1e9
+ elif '100baseT/Full' in ethtool:
+ speed = 100e6
+ elif '10baseT/Full' in ethtool:
+ speed = 10e6
+ else:
+ return (False, 'Speed not detected in '+name)
+
+ enabled = False
+ if 'Link detected: yes' in ethtool:
+ enabled = True
+
+ if speed != None and mac != None and pci_busid != None:
+ mac = mac.split(':')
+ pci_busid_split = re.split(':|\.', pci_busid)
+ #Fill the port information
+ port.set(name, virtual, enabled, speed, mac, pci_busid, pci_busid_split)
+ nic.add_port(port)
+
+ if len(nic.ports) > 0:
+ #Fill the nic model
+ if model != None:
+ nic.set_model(model)
+ else:
+ nic.set_model(nic_name)
+
+ #Add it to the topology
+ nic_topology[node_id].append(nic)
+
+ return (True, "")
+
+def get_os_information(ssh_conn, os):
+ warning_text=""
+# command = 'lsb_release -a'
+# (stdin, stdout, stderr) = ssh_conn.exec_command(command)
+# cont = 0
+# for line in stdout.readlines():
+# line_split = re.split('\t| *', line.rstrip('\n'))
+# if line_split[0] == 'Distributor' and line_split[1] == 'ID:':
+# distributor = line_split[2]
+# cont += 1
+# elif line_split[0] == 'Release:':
+# release = line_split[1]
+# cont += 1
+# elif line_split[0] == 'Codename:':
+# codename = line_split[1]
+# cont += 1
+# if cont != 3:
+# return (False, 'It was not possible to obtain the OS id')
+# id_ = distributor+'-'+release+'-'+codename
+
+
+ command = 'cat /etc/redhat-release'
+ (_, stdout, _) = ssh_conn.exec_command(command)
+ id_text= stdout.read()
+ if len(id_text)==0:
+ #try with Ubuntu
+ command = 'lsb_release -d -s'
+ (_, stdout, _) = ssh_conn.exec_command(command)
+ id_text= stdout.read()
+ if len(id_text)==0:
+ raise paramiko.ssh_exception.SSHException("Can not determinte release neither with 'lsb_release' nor with 'cat /etc/redhat-release'")
+ id_ = id_text.rstrip('\n')
+
+ command = 'uname -o'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ type_ = stdout.read().rstrip('\n')
+
+ command = 'uname -i'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error)>0:
+ raise paramiko.ssh_exception.SSHException(command +' : '+ error)
+ bit_architecture = stdout.read().rstrip('\n')
+
+ (return_status, code) = os.set(id_, type_, bit_architecture)
+ if not return_status:
+ return (return_status, code)
+ warning_text += code
+ return (True, warning_text)
+
+def get_hypervisor_information(virsh_conn, hypervisor):
+ type_ = virsh_conn.getType().rstrip('\n')
+ version = virsh_conn.getVersion()
+ lib_version = virsh_conn.getLibVersion()
+
+ domains = list()
+ tree=ElementTree.fromstring(virsh_conn.getCapabilities())
+ for target in tree.findall("guest"):
+ os_type = target.find("os_type").text
+ #We only allow full virtualization
+ if os_type != 'hvm':
+ continue
+ wordsize = int(target.find('arch/wordsize').text)
+ if wordsize == 64:
+ for domain in target.findall("arch/domain"):
+ domains.append(domain.get("type"))
+
+ (return_status, code) = hypervisor.set(type_, version, lib_version, domains)
+ if not return_status:
+ return (return_status, code)
+ return (True, code)
+
+class RADavailableResourcesClass(RADclass):
+ def __init__(self, resources):
+ """Copy resources from the RADclass (server resources not taking into account resources used by VMs"""
+ #New
+ self.reserved = dict() #Dictionary of reserved resources for a server. Key are VNFC names and values RADreservedResources
+ self.cores_consumption = None #Dictionary of cpu consumption. Key is the cpu and the value is
+
+ self.machine = resources.machine
+ self.user = resources.user
+ self.password = resources.password
+ self.name = resources.name
+ self.nr_processors = resources.nr_processors
+ self.processor_family = resources.processor_family
+ self.processor_manufacturer = resources.processor_manufacturer
+ self.processor_version = resources.processor_version
+ self.processor_features = resources.processor_features
+ self.memory_type = resources.memory_type
+ self.memory_freq = resources.memory_freq
+ self.memory_nr_channels = resources.memory_nr_channels
+ self.memory_size = resources.memory_size
+ self.memory_hugepage_sz = resources.memory_hugepage_sz
+ self.hypervisor = Hypervisor()
+ self.hypervisor.assign(resources.hypervisor)
+ self.os = OpSys()
+ self.os.assign(resources.os)
+ self.nodes = dict()
+ for node_k, node_v in resources.nodes.iteritems():
+ self.nodes[node_k] = Node()
+ self.nodes[node_k].assign(node_v)
+ return
+
+ def _get_cores_consumption_warnings(self):
+ """Returns list of warning strings in case warnings are generated.
+ In case no warnings are generated the return value will be an empty list"""
+ warnings = list()
+ #Get the cores consumption
+ (return_status, code) = get_ssh_connection(self.machine, self.user, self.password)
+ if not return_status:
+ return (return_status, code)
+ ssh_conn = code
+ command = 'mpstat -P ALL 1 1 | grep Average | egrep -v CPU\|all'
+ (_, stdout, stderr) = ssh_conn.exec_command(command)
+ error = stderr.read()
+ if len(error) > 0:
+ return (False, error)
+
+ self.cores_consumption = dict()
+ for line in stdout.readlines():
+ cpu_usage_split = re.split('\t| *', line.rstrip('\n'))
+ usage = 100 *(1 - float(cpu_usage_split[10]))
+ if usage > 0:
+ self.cores_consumption[int(cpu_usage_split[1])] = usage
+ ssh_conn.close()
+ #Check if any core marked as available in the nodes has cpu_usage > 0
+ for _, node_v in self.nodes.iteritems():
+ cores = node_v.processor.eligible_cores
+ for cpu in cores:
+ if len(cpu) > 1:
+ for core in cpu:
+ if core in self.cores_consumption:
+ warnings.append('Warning: Core '+str(core)+' is supposed to be idle but it is consuming '+str(self.cores_consumption[core])+'%')
+ else:
+ if cpu in self.cores_consumption:
+ warnings.append('Warning: Core '+str(core)+' is supposed to be idle but it is consuming '+str(self.cores_consumption[cpu])+'%')
+
+ return warnings
+
+ def reserved_to_text(self):
+ text = str()
+ for VNFC_name, VNFC_reserved in self.reserved.iteritems():
+ text += ' VNFC: '+str(VNFC_name)+'\n'
+ text += VNFC_reserved.to_text()
+
+ return text
+
+ def obtain_usage(self):
+ resp = dict()
+ #Iterate through nodes to get cores, eligible cores, memory and physical ports (save ports usage for next section)
+ nodes = dict()
+ ports_usage = dict()
+ hugepage_size = dict()
+ for node_k, node_v in self.nodes.iteritems():
+ node = dict()
+ ports_usage[node_k] = dict()
+ eligible_cores = list()
+ for pair in node_v.processor.eligible_cores:
+ if isinstance(pair, list):
+ for element in pair:
+ eligible_cores.append(element)
+ else:
+ eligible_cores.append(pair)
+ node['cpus'] = {'cores':node_v.processor.cores,'eligible_cores':eligible_cores}
+ node['memory'] = {'size':str(node_v.memory.node_size/(1024*1024*1024))+'GB','eligible':str(node_v.memory.eligible_memory/(1024*1024*1024))+'GB'}
+ hugepage_size[node_k] = node_v.memory.hugepage_sz
+
+ ports = dict()
+ for nic in node_v.nic_list:
+ for port in nic.ports.itervalues():
+ if port.enabled and not port.virtual:
+ ports[port.name] = {'speed':str(port.speed/1000000000)+'G'}
+# print '*************** ',port.name,'speed',port.speed
+ ports_usage[node_k][port.name] = 100 - int(100*float(port.available_bw)/float(port.speed))
+ node['ports'] = ports
+ nodes[node_k] = node
+ resp['RAD'] = nodes
+
+ #Iterate through reserved section to get used cores, used memory and port usage
+ cores = dict()
+ memory = dict()
+ #reserved_cores = list
+ for node_k in self.nodes.iterkeys():
+ if not node_k in cores:
+ cores[node_k] = list()
+ memory[node_k] = 0
+ for _, reserved in self.reserved.iteritems():
+ if node_k in reserved.node_reserved_resources:
+ node_v = reserved.node_reserved_resources[node_k]
+ cores[node_k].extend(node_v.reserved_cores)
+ memory[node_k] += node_v.reserved_hugepage_nr * hugepage_size[node_k]
+
+ occupation = dict()
+ for node_k in self.nodes.iterkeys():
+ ports = dict()
+ for name, usage in ports_usage[node_k].iteritems():
+ ports[name] = {'occupied':str(usage)+'%'}
+# print '****************cores',cores
+# print '****************memory',memory
+ occupation[node_k] = {'cores':cores[node_k],'memory':str(memory[node_k]/(1024*1024*1024))+'GB','ports':ports}
+ resp['occupation'] = occupation
+
+ return resp
+
+class RADreservedResources():
+ def __init__(self):
+ self.node_reserved_resources = dict() #dict. keys are the RAD nodes id, values are NodeReservedResources
+ self.mgmt_interface_pci = None #pci in the VNF for the management interface
+ self.image = None #Path in remote machine of the VNFC image
+
+ def update(self,reserved):
+ self.image = reserved.image
+ self.mgmt_interface_pci = reserved.mgmt_interface_pci
+ for k,v in reserved.node_reserved_resources.iteritems():
+ if k in self.node_reserved_resources.keys():
+ return (False, 'Duplicated node entry '+str(k)+' in reserved resources')
+ self.node_reserved_resources[k]=v
+
+ return (True, "")
+
+ def to_text(self):
+ text = ' image: '+str(self.image)+'\n'
+ for node_id, node_reserved in self.node_reserved_resources.iteritems():
+ text += ' Node ID: '+str(node_id)+'\n'
+ text += node_reserved.to_text()
+ return text
+
+class NodeReservedResources():
+ def __init__(self):
+ # reserved_shared_cores = None #list. List of all cores that the VNFC needs in shared mode #TODO Not used
+ # reserved_memory = None #Integer. Amount of KiB needed by the VNFC #TODO. Not used since hugepages are used
+ self.reserved_cores = list() #list. List of all cores that the VNFC uses
+ self.reserved_hugepage_nr = 0 #Integer. Number of hugepages needed by the VNFC
+ self.reserved_ports = dict() #dict. The key is the physical port pci and the value the VNFC port description
+ self.vlan_tags = dict()
+ self.cpu_pinning = None
+
+ def to_text(self):
+ text = ' cores: '+str(self.reserved_cores)+'\n'
+ text += ' cpu_pinning: '+str(self.cpu_pinning)+'\n'
+ text += ' hugepages_nr: '+str(self.reserved_hugepage_nr)+'\n'
+ for port_pci, port_description in self.reserved_ports.iteritems():
+ text += ' port: '+str(port_pci)+'\n'
+ text += port_description.to_text()
+ return text
+
+# def update(self,reserved):
+# self.reserved_cores = list(reserved.reserved_cores)
+# self.reserved_hugepage_nr = reserved.reserved_hugepage_nr
+# self.reserved_ports = dict(reserved.reserved_ports)
+# self.cpu_pinning = list(reserved.cpu_pinning)
+
+
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Common usuful functions
+'''
+
+__author__="Alfonso Tierno, Pablo Montes"
+__date__ ="$10-jul-2014 12:07:15$"
+
+
+import yaml
+import paramiko
+from definitionsClass import definitionsClass
+from definitionsClass import Units
+import random
+from jsonschema import validate as js_v, exceptions as js_e
+
+def check_and_convert_units(value, value_type):
+ """TODO: Update description
+ This method receives a text with 2 fields using a blank as separator and a list of valid units. The first field must represent a number
+ and the second one units.
+ In case the second field is not one of valid_units (False, <error description>) is returned.
+ In case the second field is a valid unit the first number is converted in the following way:
+ Gbps, Mbps, kbps -> Mbps
+ GB,MB,KB,B,GiB,MiB,KiB -> B
+ GHz,MHz,KHz,Hz -> Hz
+ If conversion is done successfully (True, <converted value>) is returned"""
+ try:
+ if value_type == Units.no_units:
+ if not isinstance(value,int) and not isinstance(value,float):
+ return (False, 'When no units are used only an integer or float must be used')
+ elif value_type == Units.name:
+ if not isinstance(value,str):
+ return (False, 'For names str must be used')
+ elif value_type == Units.boolean:
+ if not isinstance(value,bool):
+ return (False, 'A boolean or Yes/No mut be used')
+ else:
+ splitted = value.split(' ')
+ if len(splitted) != 2:
+ return (False, 'Expected format: <value> <units>')
+ (value, units) = splitted
+ if ',' in value or '.' in value:
+ return (False, 'Use integers to represent numeric values')
+
+ value = int(value)
+
+# if not isinstance(value_type, Units):
+# return (False, 'Not valid value_type')
+
+ valid_units = definitionsClass.units[value_type]
+
+ #Convert everything to upper in order to make comparations easier
+ units = units.upper()
+ for i in range(0, len(valid_units)):
+ valid_units[i] = valid_units[i].upper()
+
+ #Check the used units are valid ones
+ if units not in valid_units:
+ return (False, 'Valid units are: '+', '.join(valid_units))
+
+ if units.startswith('GI'):
+ value = value *1024*1024*1024
+ elif units.startswith('MI'):
+ value = value *1024*1024
+ elif units.startswith('KI'):
+ value = value *1024
+ elif units.startswith('G'):
+ value = value *1000000000
+ elif units.startswith('M'):
+ value = value *1000000
+ elif units.startswith('K'):
+ value = value *1000
+ except Exception,e:
+ return (False, 'Unexpected error in auxiliary_functions.py - check_and_convert_units:\n'+str(e))
+
+ return (True, value)
+
+def get_ssh_connection(machine, user=None, password=None):
+ """Stablishes an ssh connection to the remote server. Returns (True, paramiko_ssh) in case of success or (False, <error message>) in case of error"""
+ try:
+ s = paramiko.SSHClient()
+ s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ s.load_system_host_keys()
+ s.connect(machine, 22, user, password, timeout=10)
+ except Exception,e:
+ return (False, 'It was not possible to connect to '+machine+str(e))
+
+ return (True, s)
+
+def run_in_remote_server(s,command):
+ """Runs in the remote server the specified command. Returns (True, stdout) in case of success or (False, <error message>) in case of error"""
+ try:
+ (_, stdout, stderr) = s.exec_command(command)
+ error_msg = stderr.read()
+ if len(error_msg) > 0:
+ return (False, error_msg)
+ except Exception,e:
+ return (False, str(e))
+
+ return (True, stdout)
+
+def read_file(file_):
+ """Reads a file specified by 'file' and returns (True,<its content as a string>) in case of success or (False, <error message>) in case of failure"""
+ try:
+ f = open(file_, 'r')
+ read_data = f.read()
+ f.close()
+ except Exception,e:
+ return (False, str(e))
+
+ return (True, read_data)
+
+def check_contains(element, keywords):
+ """Auxiliary function used to check if a yaml structure contains or not
+ an specific field. Returns a bool"""
+ for key in keywords:
+ if not key in element:
+ return False
+ return True
+
+def check_contains_(element, keywords):
+ """Auxiliary function used to check if a yaml structure contains or not
+ an specific field. Returns a bool,missing_variables"""
+ for key in keywords:
+ if not key in element:
+ return False, key
+ return True, None
+
+def write_file(file_, content):
+ """Generates a file specified by 'file' and fills it using 'content'"""
+ f = open(file_, 'w')
+ f.write(content)
+ f.close()
+
+def nice_print(yaml_element):
+ """Print a yaml structure. Used mainly for debugging"""
+ print(yaml.dump(yaml_element, default_flow_style=False))
+
+def new_random_mac():
+ mac = (0xE2, random.randint(0x00, 0xff), random.randint(0x00, 0xff), random.randint(0x00, 0xff), random.randint(0x00, 0xff), random.randint(0x00, 0xff) )
+ return ':'.join(map(lambda x: "%02X" % x, mac))
+
+def parse_dict(var, template):
+ if type(var) is not dict: return -1, 'not a dictionary'
+ for _,tv in template.items():
+ if type(tv) is list:
+ return
+
+def delete_nulls(var):
+ if type(var) is dict:
+ for k in var.keys():
+ if var[k] is None: del var[k]
+ elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
+ if delete_nulls(var[k]): del var[k]
+ if len(var) == 0: return True
+ elif type(var) is list or type(var) is tuple:
+ for k in var:
+ if type(k) is dict: delete_nulls(k)
+ if len(var) == 0: return True
+ return False
+
+def get_next_2pow(var):
+ if var==0: return 0
+ v=1
+ while v<var: v=v*2
+ return v
+
+def check_valid_uuid(uuid):
+ id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
+ try:
+ js_v(uuid, id_schema)
+ return True
+ except js_e.ValidationError:
+ return False
+
+def DeleteNone(var):
+ '''Removes recursively empty dictionaries or lists
+ return True if var is an empty dict or list '''
+ if type(var) is dict:
+ for k in var.keys():
+ if var[k] is None: del var[k]
+ elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
+ if DeleteNone(var[k]): del var[k]
+ if len(var) == 0: return True
+ elif type(var) is list or type(var) is tuple:
+ for k in var:
+ if type(k) is dict: DeleteNone(k)
+ if len(var) == 0: return True
+ return False
+
+def gen_random_mac():
+ '''generates a random mac address. Avoid multicast, broadcast, etc
+ '''
+ mac = (
+ #52,54,00,
+ #2 + 4*random.randint(0x00, 0x3f), #4 multiple, unicast local mac address
+ 0x52,
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)
+ )
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Definitions of classes for the Host operating server, ...
+'''
+
+__author__="Pablo Montes"
+
+
+class Units():
+ memory_1000 = 1
+ memory_1024 = 2
+ memory_full = 3
+ bw = 4
+ freq = 5
+ no_units = 6
+ name = 7
+ boolean = 8
+
+class definitionsClass():
+ user = 'n2'
+ password = 'n2'
+ extrict_hugepages_allocation = True
+ processor_possible_features = ['64b','iommu','lps','tlbps','hwsv','dioc','ht']
+ processor_possible_manufacturers = ['Intel','AMD']
+ processor_possible_families = ['Xeon']
+ processor_possible_versions = ['Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz', 'Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz','Intel(R) Xeon(R) CPU E5-2697 v2 @ 2.70GHz']
+ memory_possible_types = ['DDR2','DDR3']
+ memory_possible_form_factors = ['DIMM']
+ hypervisor_possible_types = ['QEMU']
+ hypervisor_possible_domain_types = ['kvm'] #['qemu', 'kvm']
+ os_possible_id = ['Red Hat Enterprise Linux Server release 6.4 (Santiago)',
+ 'Red Hat Enterprise Linux Server release 6.5 (Santiago)',
+ 'Red Hat Enterprise Linux Server release 6.6 (Santiago)',
+ 'CentOS release 6.5 (Final)',
+ 'CentOS release 6.6 (Final)',
+ 'Red Hat Enterprise Linux Server release 7.0 (Maipo)',
+ 'Red Hat Enterprise Linux Server release 7.1 (Maipo)',
+ ]
+ os_possible_types = ['GNU/Linux']
+ os_possible_architectures = ['x86_64']
+ hypervisor_possible_composed_versions = ['QEMU-kvm']
+ units = dict()
+ units[Units.bw] = ['Gbps', 'Mbps', 'kbps', 'bps']
+ units[Units.freq] = ['GHz', 'MHz', 'KHz', 'Hz']
+ units[Units.memory_1000] = ['GB', 'MB', 'KB', 'B']
+ units[Units.memory_1024] = ['GiB', 'MiB', 'KiB', 'B']
+ units[Units.memory_full] = ['GB', 'MB', 'KB', 'GiB', 'MiB', 'KiB', 'B']
+ valid_hugepage_sz = [1073741824, 2097152] #In bytes
+ valid_VNFC_iface_types = ['mgmt','data']
+
+ def __init__(self):
+ return
+
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+This module interact with the openvim database,
+It implements general table management
+and complex writings 'transactional' sures,
+that is, or all is changed or nothing
+'''
+
+__author__="Alfonso Tierno"
+__date__ ="$10-jul-2014 12:07:15$"
+
+import MySQLdb as mdb
+import uuid as myUuid
+from utils import auxiliary_functions as af
+import json
+import logging
+
+HTTP_Bad_Request = 400
+HTTP_Unauthorized = 401
+HTTP_Not_Found = 404
+HTTP_Method_Not_Allowed = 405
+HTTP_Request_Timeout = 408
+HTTP_Conflict = 409
+HTTP_Service_Unavailable = 503
+HTTP_Internal_Server_Error = 500
+
+
+class vim_db():
+ def __init__(self, vlan_range, debug="ERROR"):
+ '''vlan_range must be a tuple (vlan_ini, vlan_end) with available vlan values for networks
+ every dataplane network contain a unique value, regardless of it is used or not
+ '''
+ #initialization
+ self.net_vlan_range = vlan_range
+ self.net_vlan_usedlist = None
+ self.net_vlan_lastused = self.net_vlan_range[0] -1
+ self.debug=debug
+ self.logger = logging.getLogger('vim.db')
+ self.logger.setLevel( getattr(logging, debug) )
+
+
+ def connect(self, host=None, user=None, passwd=None, database=None):
+ '''Connect to the concrete data base.
+ The first time a valid host, user, passwd and database must be provided,
+ Following calls can skip this parameters
+ '''
+ try:
+ if host is not None: self.host = host
+ if user is not None: self.user = user
+ if passwd is not None: self.passwd = passwd
+ if database is not None: self.database = database
+
+ self.con = mdb.connect(self.host, self.user, self.passwd, self.database)
+ self.logger.debug("connected to DB %s at %s@%s", self.database,self.user, self.host)
+ return 0
+ except mdb.Error as e:
+ self.logger.error("Cannot connect to DB %s at %s@%s Error %d: %s", self.database, self.user, self.host, e.args[0], e.args[1])
+ return -1
+
+ def get_db_version(self):
+ ''' Obtain the database schema version.
+ Return: (negative, text) if error or version 0.0 where schema_version table is missing
+ (version_int, version_text) if ok
+ '''
+ cmd = "SELECT version_int,version,openvim_ver FROM schema_version"
+ for retry_ in range(0,2):
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ rows = self.cur.fetchall()
+ highest_version_int=0
+ highest_version=""
+ #print rows
+ for row in rows: #look for the latest version
+ if row[0]>highest_version_int:
+ highest_version_int, highest_version = row[0:2]
+ return highest_version_int, highest_version
+ except (mdb.Error, AttributeError) as e:
+ self.logger.error("get_db_version DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd)
+ r,c = self.format_error(e)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def disconnect(self):
+ '''disconnect from the data base'''
+ try:
+ self.con.close()
+ del self.con
+ except mdb.Error as e:
+ self.logger.error("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1])
+ return -1
+ except AttributeError as e: #self.con not defined
+ if e[0][-5:] == "'con'": return -1, "Database internal error, no connection."
+ else: raise
+
+ def format_error(self, e, func, cmd, command=None, extra=None):
+ '''Creates a text error base on the produced exception
+ Params:
+ e: mdb exception
+ func: name of the function that makes the call, for logging purposes
+ cmd: database command that produce the exception
+ command: if the intention is update or delete
+ extra: extra information to add to some commands
+ Return
+ HTTP error in negative, formatted error text
+ '''
+
+ self.logger.error("%s DB Exception %s. Command %s",func, str(e), cmd)
+ if type(e[0]) is str:
+ if e[0][-5:] == "'con'": return -HTTP_Internal_Server_Error, "DB Exception, no connection."
+ else: raise
+ if e.args[0]==2006 or e.args[0]==2013 : #MySQL server has gone away (((or))) Exception 2013: Lost connection to MySQL server during query
+ #reconnect
+ self.connect()
+ return -HTTP_Request_Timeout,"Database reconnection. Try Again"
+ fk=e.args[1].find("foreign key constraint fails")
+ if fk>=0:
+ if command=="update": return -HTTP_Bad_Request, "tenant_id %s not found." % extra
+ elif command=="delete": return -HTTP_Bad_Request, "Resource is not free. There are %s that prevent its deletion." % extra
+ de = e.args[1].find("Duplicate entry")
+ fk = e.args[1].find("for key")
+ uk = e.args[1].find("Unknown column")
+ wc = e.args[1].find("in 'where clause'")
+ fl = e.args[1].find("in 'field list'")
+ #print de, fk, uk, wc,fl
+ if de>=0:
+ if fk>=0: #error 1062
+ return -HTTP_Conflict, "Value %s already in use for %s" % (e.args[1][de+15:fk], e.args[1][fk+7:])
+ if uk>=0:
+ if wc>=0:
+ return -HTTP_Bad_Request, "Field %s can not be used for filtering" % e.args[1][uk+14:wc]
+ if fl>=0:
+ return -HTTP_Bad_Request, "Field %s does not exist" % e.args[1][uk+14:wc]
+ return -HTTP_Internal_Server_Error, "Database internal Error %d: %s" % (e.args[0], e.args[1])
+
+ def __data2db_format(self, data):
+ '''convert data to database format. If data is None it return the 'Null' text,
+ otherwise it return the text surrounded by quotes ensuring internal quotes are escaped'''
+ if data==None:
+ return 'Null'
+ out=str(data)
+ if "'" not in out:
+ return "'" + out + "'"
+ elif '"' not in out:
+ return '"' + out + '"'
+ else:
+ return json.dumps(out)
+
+ def __get_used_net_vlan(self):
+ #get used from database if needed
+ try:
+ cmd = "SELECT vlan FROM nets WHERE vlan>='%s' and (type='ptp' or type='data') ORDER BY vlan LIMIT 25" % self.net_vlan_lastused
+ with self.con:
+ self.cur = self.con.cursor()
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ vlan_tuple = self.cur.fetchall()
+ #convert a tuple of tuples in a list of numbers
+ self.net_vlan_usedlist = []
+ for k in vlan_tuple:
+ self.net_vlan_usedlist.append(k[0])
+ return 0
+ except (mdb.Error, AttributeError) as e:
+ return self.format_error(e, "get_free_net_vlan", cmd)
+
+ def get_free_net_vlan(self):
+ '''obtain a vlan not used in any net'''
+
+ while True:
+ self.logger.debug("net_vlan_lastused:%d net_vlan_range:%d-%d net_vlan_usedlist:%s",
+ self.net_vlan_lastused, self.net_vlan_range[0], self.net_vlan_range[1], str(self.net_vlan_usedlist))
+ self.net_vlan_lastused += 1
+ if self.net_vlan_lastused == self.net_vlan_range[1]:
+ #start from the begining
+ self.net_vlan_lastused = self.net_vlan_range[0]
+ self.net_vlan_usedlist = None
+ if self.net_vlan_usedlist is None \
+ or (len(self.net_vlan_usedlist)>0 and self.net_vlan_lastused >= self.net_vlan_usedlist[-1] and len(self.net_vlan_usedlist)==25):
+ r = self.__get_used_net_vlan()
+ if r<0: return r
+ self.logger.debug("new net_vlan_usedlist %s", str(self.net_vlan_usedlist))
+ if self.net_vlan_lastused in self.net_vlan_usedlist:
+ continue
+ else:
+ return self.net_vlan_lastused
+
+ def get_table(self, **sql_dict):
+ ''' Obtain rows from a table.
+ Atribure sql_dir: dictionary with the following key: value
+ 'SELECT': [list of fields to retrieve] (by default all)
+ 'FROM': string of table name (Mandatory)
+ 'WHERE': dict of key:values, translated to key=value AND ... (Optional)
+ 'WHERE_NOT': dict of key:values, translated to key!=value AND ... (Optional)
+ 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
+ 'LIMIT': limit of number of rows (Optional)
+ Return: a list with dictionarys at each row
+ '''
+ #print sql_dict
+ select_= "SELECT " + ("*" if 'SELECT' not in sql_dict else ",".join(map(str,sql_dict['SELECT'])) )
+ #print 'select_', select_
+ from_ = "FROM " + str(sql_dict['FROM'])
+ #print 'from_', from_
+
+ where_and = None
+ where_or = None
+ if 'WHERE' in sql_dict and len(sql_dict['WHERE']) > 0:
+ w=sql_dict['WHERE']
+ where_and = " AND ".join(map( lambda x: str(x) + (" is Null" if w[x] is None else "='"+str(w[x])+"'"), w.keys()) )
+ if 'WHERE_NOT' in sql_dict and len(sql_dict['WHERE_NOT']) > 0:
+ w=sql_dict['WHERE_NOT']
+ where_and_not = " AND ".join(map( lambda x: str(x) + (" is not Null" if w[x] is None else "!='"+str(w[x])+"'"), w.keys()) )
+ if where_and:
+ where_and += " AND " + where_and_not
+ else:
+ where_and = where_and_not
+ if 'WHERE_OR' in sql_dict and len(sql_dict['WHERE_OR']) > 0:
+ w=sql_dict['WHERE_OR']
+ where_or = " OR ".join(map( lambda x: str(x) + (" is Null" if w[x] is None else "='"+str(w[x])+"'"), w.keys()) )
+
+ if where_and!=None and where_or!=None:
+ where_ = "WHERE (" + where_and + ") OR " + where_or
+ elif where_and!=None and where_or==None:
+ where_ = "WHERE " + where_and
+ elif where_and==None and where_or!=None:
+ where_ = "WHERE " + where_or
+ else:
+ where_ = ""
+ #print 'where_', where_
+ limit_ = "LIMIT " + str(sql_dict['LIMIT']) if 'LIMIT' in sql_dict else ""
+ #print 'limit_', limit_
+ cmd = " ".join( (select_, from_, where_, limit_) )
+ for retry_ in range(0,2):
+ try:
+ with self.con:
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ rows = self.cur.fetchall()
+ return self.cur.rowcount, rows
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "get_table", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_tenant(self, tenant_dict):
+ ''' Add one row into a table.
+ Attribure
+ tenant_dict: dictionary with the key: value to insert
+ It checks presence of uuid and add one automatically otherwise
+ Return: (result, uuid) where result can be 0 if error, or 1 if ok
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ inserted=-1
+ try:
+ #create uuid if not provided
+ if 'uuid' not in tenant_dict:
+ uuid = tenant_dict['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else:
+ uuid = str(tenant_dict['uuid'])
+ #obtain tenant_id for logs
+ tenant_id = uuid
+ with self.con:
+ self.cur = self.con.cursor()
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, used_at) VALUES ('%s','tenants')" % uuid
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #insert tenant
+ cmd= "INSERT INTO tenants (" + \
+ ",".join(map(str, tenant_dict.keys() )) + ") VALUES(" + \
+ ",".join(map(lambda x: "Null" if x is None else "'"+str(x)+"'",tenant_dict.values() )) + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ inserted = self.cur.rowcount
+ ##inserting new log
+ #del tenant_dict['uuid'] # not interested for the log
+ #cmd = "INSERT INTO logs (related,level,tenant_id,uuid,description) VALUES ('tenants','debug','%s','%s',\"new tenant %s\")" % (uuid, tenant_id, str(tenant_dict))
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+ #commit transaction
+ self.cur.close()
+ if inserted == 0: return 0, uuid
+ with self.con:
+ self.cur = self.con.cursor()
+ #adding public flavors
+ cmd = "INSERT INTO tenants_flavors(flavor_id,tenant_id) SELECT uuid as flavor_id,'"+ tenant_id + "' FROM flavors WHERE public = 'yes'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ self.logger.debug("attached public flavors: %s", str(self.cur.rowcount))
+ #rows = self.cur.fetchall()
+ #for row in rows:
+ # cmd = "INSERT INTO tenants_flavors(flavor_id,tenant_id) VALUES('%s','%s')" % (row[0], tenant_id)
+ # self.cur.execute(cmd )
+ #adding public images
+ cmd = "INSERT INTO tenants_images(image_id,tenant_id) SELECT uuid as image_id,'"+ tenant_id + "' FROM images WHERE public = 'yes'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ self.logger.debug("attached public images: %s", str(self.cur.rowcount))
+ return 1, uuid
+ except (mdb.Error, AttributeError) as e:
+ if inserted==1:
+ self.logger.warning("new_tenant DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd)
+ return 1, uuid
+ else:
+ r,c = self.format_error(e, "new_tenant", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_row(self, table, INSERT, add_uuid=False, log=False):
+ ''' Add one row into a table.
+ Atribure
+ INSERT: dictionary with the key: value to insert
+ table: table where to insert
+ add_uuid: if True, it will crated an uuid key entry at INSERT if not provided
+ It checks presence of uuid and add one automatically otherwise
+ Return: (result, uuid) where result can be 0 if error, or 1 if ok
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ if add_uuid:
+ #create uuid if not provided
+ if 'uuid' not in INSERT:
+ uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else:
+ uuid = str(INSERT['uuid'])
+ else:
+ uuid=None
+ with self.con:
+ self.cur = self.con.cursor()
+ if add_uuid:
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, used_at) VALUES ('%s','%s')" % (uuid, table)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #insertion
+ cmd= "INSERT INTO " + table +" (" + \
+ ",".join(map(str, INSERT.keys() )) + ") VALUES(" + \
+ ",".join(map(lambda x: 'Null' if x is None else "'"+str(x)+"'", INSERT.values() )) + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ nb_rows = self.cur.rowcount
+ #inserting new log
+ #if nb_rows > 0 and log:
+ # if add_uuid: del INSERT['uuid']
+ # #obtain tenant_id for logs
+ # if 'tenant_id' in INSERT:
+ # tenant_id = INSERT['tenant_id']
+ # del INSERT['tenant_id']
+ # elif table == 'tenants':
+ # tenant_id = uuid
+ # else:
+ # tenant_id = None
+ # if uuid is None: uuid_k = uuid_v = ""
+ # else: uuid_k=",uuid"; uuid_v=",'" + str(uuid) + "'"
+ # if tenant_id is None: tenant_k = tenant_v = ""
+ # else: tenant_k=",tenant_id"; tenant_v=",'" + str(tenant_id) + "'"
+ # cmd = "INSERT INTO logs (related,level%s%s,description) VALUES ('%s','debug'%s%s,\"new %s %s\")" \
+ # % (uuid_k, tenant_k, table, uuid_v, tenant_v, table[:-1], str(INSERT))
+ # self.logger.debug(cmd)
+ # self.cur.execute(cmd)
+ return nb_rows, uuid
+
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "new_row", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def __remove_quotes(self, data):
+ '''remove single quotes ' of any string content of data dictionary'''
+ for k,v in data.items():
+ if type(v) == str:
+ if "'" in v:
+ data[k] = data[k].replace("'","_")
+
+ def _update_rows_internal(self, table, UPDATE, WHERE={}):
+ cmd= "UPDATE " + table +" SET " + \
+ ",".join(map(lambda x: str(x)+'='+ self.__data2db_format(UPDATE[x]), UPDATE.keys() ));
+ if WHERE:
+ cmd += " WHERE " + " and ".join(map(lambda x: str(x)+ (' is Null' if WHERE[x] is None else"='"+str(WHERE[x])+"'" ), WHERE.keys() ))
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ nb_rows = self.cur.rowcount
+ return nb_rows, None
+
+ def update_rows(self, table, UPDATE, WHERE={}, log=False):
+ ''' Update one or several rows into a table.
+ Atributes
+ UPDATE: dictionary with the key-new_value pairs to change
+ table: table to be modified
+ WHERE: dictionary to filter target rows, key-value
+ log: if true, a log entry is added at logs table
+ Return: (result, None) where result indicates the number of updated files
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ #gettting uuid
+ uuid = WHERE.get('uuid')
+
+ with self.con:
+ self.cur = self.con.cursor()
+ cmd= "UPDATE " + table +" SET " + \
+ ",".join(map(lambda x: str(x)+'='+ self.__data2db_format(UPDATE[x]), UPDATE.keys() ));
+ if WHERE:
+ cmd += " WHERE " + " and ".join(map(lambda x: str(x)+ (' is Null' if WHERE[x] is None else"='"+str(WHERE[x])+"'" ), WHERE.keys() ))
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ nb_rows = self.cur.rowcount
+ #if nb_rows > 0 and log:
+ # #inserting new log
+ # if uuid is None: uuid_k = uuid_v = ""
+ # else: uuid_k=",uuid"; uuid_v=",'" + str(uuid) + "'"
+ # cmd = "INSERT INTO logs (related,level%s,description) VALUES ('%s','debug'%s,\"updating %d entry %s\")" \
+ # % (uuid_k, table, uuid_v, nb_rows, (str(UPDATE)).replace('"','-') )
+ # self.logger.debug(cmd)
+ # self.cur.execute(cmd)
+ return nb_rows, uuid
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "update_rows", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def get_host(self, host_id):
+ if af.check_valid_uuid(host_id):
+ where_filter="uuid='" + host_id + "'"
+ else:
+ where_filter="name='" + host_id + "'"
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ #get HOST
+ cmd = "SELECT uuid, user, name, ip_name, description, ranking, admin_state_up, DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s') as created_at \
+ FROM hosts WHERE " + where_filter
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount == 0 :
+ return 0, "host '" + str(host_id) +"'not found."
+ elif self.cur.rowcount > 1 :
+ return 0, "host '" + str(host_id) +"' matches more than one result."
+ host = self.cur.fetchone()
+ host_id = host['uuid']
+ #get numa
+ cmd = "SELECT id, numa_socket, hugepages, memory, admin_state_up FROM numas WHERE host_id = '" + str(host_id) + "'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ host['numas'] = self.cur.fetchall()
+ for numa in host['numas']:
+ #print "SELECT core_id, instance_id, status, thread_id, v_thread_id FROM resources_core WHERE numa_id = '" + str(numa['id']) + "'"
+ #get cores
+ cmd = "SELECT core_id, instance_id, status, thread_id, v_thread_id FROM resources_core WHERE numa_id = '" + str(numa['id']) + "'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ numa['cores'] = self.cur.fetchall()
+ for core in numa['cores']:
+ if core['instance_id'] == None: del core['instance_id'], core['v_thread_id']
+ if core['status'] == 'ok': del core['status']
+ #get used memory
+ cmd = "SELECT sum(consumed) as hugepages_consumed FROM resources_mem WHERE numa_id = '" + str(numa['id']) + "' GROUP BY numa_id"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ used = self.cur.fetchone()
+ used_= int(used['hugepages_consumed']) if used != None else 0
+ numa['hugepages_consumed'] = used_
+ #get ports
+ #cmd = "CALL GetPortsFromNuma(%s)'" % str(numa['id'])
+ #self.cur.callproc('GetPortsFromNuma', (numa['id'],) )
+ #every time a Procedure is launched you need to close and open the cursor
+ #under Error 2014: Commands out of sync; you can't run this command now
+ #self.cur.close()
+ #self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ cmd="SELECT Mbps, pci, status, Mbps_used, instance_id, if(id=root_id,'PF','VF') as type_,\
+ switch_port, switch_dpid, mac, source_name\
+ FROM resources_port WHERE numa_id=%d ORDER BY root_id, type_ DESC" % (numa['id'])
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ifaces = self.cur.fetchall()
+ #The SQL query will ensure to have SRIOV interfaces from a port first
+ sriovs=[]
+ Mpbs_consumed = 0
+ numa['interfaces'] = []
+ for iface in ifaces:
+ if not iface["instance_id"]:
+ del iface["instance_id"]
+ if iface['status'] == 'ok':
+ del iface['status']
+ Mpbs_consumed += int(iface["Mbps_used"])
+ del iface["Mbps_used"]
+ if iface["type_"]=='PF':
+ if not iface["switch_dpid"]:
+ del iface["switch_dpid"]
+ if not iface["switch_port"]:
+ del iface["switch_port"]
+ if sriovs:
+ iface["sriovs"] = sriovs
+ if Mpbs_consumed:
+ iface["Mpbs_consumed"] = Mpbs_consumed
+ del iface["type_"]
+ numa['interfaces'].append(iface)
+ sriovs=[]
+ Mpbs_consumed = 0
+ else: #VF, SRIOV
+ del iface["switch_port"]
+ del iface["switch_dpid"]
+ del iface["type_"]
+ del iface["Mbps"]
+ sriovs.append(iface)
+
+ #delete internal field
+ del numa['id']
+ return 1, host
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "get_host", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_uuid(self):
+ max_retries=10
+ while max_retries>0:
+ uuid = str( myUuid.uuid1() )
+ if self.check_uuid(uuid)[0] == 0:
+ return uuid
+ max_retries-=1
+ return uuid
+
+ def check_uuid(self, uuid):
+ '''check in the database if this uuid is already present'''
+ try:
+ cmd = "SELECT * FROM uuids where uuid='" + str(uuid) + "'"
+ with self.con:
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ rows = self.cur.fetchall()
+ return self.cur.rowcount, rows
+ except (mdb.Error, AttributeError) as e:
+ return self.format_error(e, "check_uuid", cmd)
+
+ def __get_next_ids(self):
+ '''get next auto increment index of all table in the database'''
+ self.cur.execute("SELECT table_name,AUTO_INCREMENT FROM information_schema.tables WHERE AUTO_INCREMENT IS NOT NULL AND table_schema = DATABASE()")
+ rows = self.cur.fetchall()
+ return self.cur.rowcount, dict(rows)
+
+ def edit_host(self, host_id, host_dict):
+ #get next port index
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ #update table host
+ numa_list = host_dict.pop('numas', () )
+ if host_dict:
+ self._update_rows_internal("hosts", host_dict, {"uuid": host_id})
+
+ where = {"host_id": host_id}
+ for numa_dict in numa_list:
+ where["numa_socket"] = str(numa_dict.pop('numa_socket'))
+ interface_list = numa_dict.pop('interfaces', () )
+ if numa_dict:
+ self._update_rows_internal("numas", numa_dict, where)
+ for interface in interface_list:
+ source_name = str(interface.pop("source_name") )
+ if interface:
+ #get interface id from resources_port
+ cmd= "SELECT rp.id as id FROM resources_port as rp join numas as n on n.id=rp.numa_id join hosts as h on h.uuid=n.host_id " +\
+ "WHERE host_id='%s' and rp.source_name='%s'" %(host_id, source_name)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ row = self.cur.fetchone()
+ if self.cur.rowcount<=0:
+ return -HTTP_Bad_Request, "Interface source_name='%s' from numa_socket='%s' not found" % (source_name, str(where["numa_socket"]))
+ interface_id = row[0]
+ self._update_rows_internal("resources_port", interface, {"root_id": interface_id})
+ return self.get_host(host_id)
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "edit_host", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_host(self, host_dict):
+ #get next port index
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ result, next_ids = self.__get_next_ids()
+ #print "next_ids: " + str(next_ids)
+ if result <= 0: return result, "Internal DataBase error getting next id of tables"
+
+ #create uuid if not provided
+ if 'uuid' not in host_dict:
+ uuid = host_dict['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else: #check uuid is valid
+ uuid = str(host_dict['uuid'])
+ # result, data = self.check_uuid(uuid)
+ # if (result == 1):
+ # return -1, "UUID '%s' already in use" % uuid
+
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, used_at) VALUES ('%s','hosts')" % uuid
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+
+ #insert in table host
+ numa_list = host_dict.pop('numas', [])
+ #get nonhupages and nonisolated cpus
+ host_dict['RAM']=0
+ host_dict['cpus']=0
+ for numa in numa_list:
+ mem_numa = numa.get('memory', 0) - numa.get('hugepages',0)
+ if mem_numa>0:
+ host_dict['RAM'] += mem_numa
+ for core in numa.get("cores", []):
+ if "status" in core and core["status"]=="noteligible":
+ host_dict['cpus']+=1
+ host_dict['RAM']*=1024 # from GB to MB
+
+ keys = ",".join(host_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", host_dict.values() ) )
+ cmd = "INSERT INTO hosts (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+ #if result != 1: return -1, "Database Error while inserting at hosts table"
+
+ #insert numas
+ nb_numas = nb_cores = nb_ifaces = 0
+ for numa_dict in numa_list:
+ nb_numas += 1
+ interface_list = numa_dict.pop('interfaces', [])
+ core_list = numa_dict.pop('cores', [])
+ numa_dict['id'] = next_ids['numas']; next_ids['numas'] += 1
+ numa_dict['host_id'] = uuid
+ keys = ",".join(numa_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", numa_dict.values() ) )
+ cmd = "INSERT INTO numas (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+
+ #insert cores
+ for core_dict in core_list:
+ nb_cores += 1
+ core_dict['numa_id'] = numa_dict['id']
+ keys = ",".join(core_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", core_dict.values() ) )
+ cmd = "INSERT INTO resources_core (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+
+ #insert ports
+ for port_dict in interface_list:
+ nb_ifaces += 1
+ sriov_list = port_dict.pop('sriovs', [])
+ port_dict['numa_id'] = numa_dict['id']
+ port_dict['id'] = port_dict['root_id'] = next_ids['resources_port']
+ next_ids['resources_port'] += 1
+ switch_port = port_dict.get('switch_port', None)
+ switch_dpid = port_dict.get('switch_dpid', None)
+ keys = ",".join(port_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", port_dict.values() ) )
+ cmd = "INSERT INTO resources_port (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+
+ #insert sriovs into port table
+ for sriov_dict in sriov_list:
+ sriov_dict['switch_port'] = switch_port
+ sriov_dict['switch_dpid'] = switch_dpid
+ sriov_dict['numa_id'] = port_dict['numa_id']
+ sriov_dict['Mbps'] = port_dict['Mbps']
+ sriov_dict['root_id'] = port_dict['id']
+ sriov_dict['id'] = next_ids['resources_port']
+ if "vlan" in sriov_dict:
+ del sriov_dict["vlan"]
+ next_ids['resources_port'] += 1
+ keys = ",".join(sriov_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", sriov_dict.values() ) )
+ cmd = "INSERT INTO resources_port (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ result = self.cur.execute(cmd)
+
+ #inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,description) VALUES ('hosts','debug','%s','new host: %d numas, %d theads, %d ifaces')" % (uuid, nb_numas, nb_cores, nb_ifaces)
+ #self.logger.debug(cmd)
+ #result = self.cur.execute(cmd)
+
+ #inseted ok
+ with self.con:
+ self.cur = self.con.cursor()
+ self.logger.debug("callproc('UpdateSwitchPort', () )")
+ self.cur.callproc('UpdateSwitchPort', () )
+
+ self.logger.debug("getting host '%s'",str(host_dict['uuid']))
+ return self.get_host(host_dict['uuid'])
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "new_host", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_flavor(self, flavor_dict, tenant_id ):
+ '''Add new flavor into the database. Create uuid if not provided
+ Atributes
+ flavor_dict: flavor dictionary with the key: value to insert. Must be valid flavors columns
+ tenant_id: if not 'any', it matches this flavor/tenant inserting at tenants_flavors table
+ Return: (result, data) where result can be
+ negative: error at inserting. data contain text
+ 1, inserted, data contain inserted uuid flavor
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ #create uuid if not provided
+ if 'uuid' not in flavor_dict:
+ uuid = flavor_dict['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else: #check uuid is valid
+ uuid = str(flavor_dict['uuid'])
+ # result, data = self.check_uuid(uuid)
+ # if (result == 1):
+ # return -1, "UUID '%s' already in use" % uuid
+
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, used_at) VALUES ('%s','flavors')" % uuid
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ #insert in table flavor
+ keys = ",".join(flavor_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", flavor_dict.values() ) )
+ cmd = "INSERT INTO flavors (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #if result != 1: return -1, "Database Error while inserting at flavors table"
+
+ #insert tenants_flavors
+ if tenant_id != 'any':
+ cmd = "INSERT INTO tenants_flavors (tenant_id,flavor_id) VALUES ('%s','%s')" % (tenant_id, uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ #inserting new log
+ #del flavor_dict['uuid']
+ #if 'extended' in flavor_dict: del flavor_dict['extended'] #remove two many information
+ #cmd = "INSERT INTO logs (related,level,uuid, tenant_id, description) VALUES ('flavors','debug','%s','%s',\"new flavor: %s\")" \
+ # % (uuid, tenant_id, str(flavor_dict))
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+
+ #inseted ok
+ return 1, uuid
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "new_flavor", cmd, "update", tenant_id)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_image(self, image_dict, tenant_id):
+ '''Add new image into the database. Create uuid if not provided
+ Atributes
+ image_dict: image dictionary with the key: value to insert. Must be valid images columns
+ tenant_id: if not 'any', it matches this image/tenant inserting at tenants_images table
+ Return: (result, data) where result can be
+ negative: error at inserting. data contain text
+ 1, inserted, data contain inserted uuid image
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ #create uuid if not provided
+ if 'uuid' not in image_dict:
+ uuid = image_dict['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else: #check uuid is valid
+ uuid = str(image_dict['uuid'])
+ # result, data = self.check_uuid(uuid)
+ # if (result == 1):
+ # return -1, "UUID '%s' already in use" % uuid
+
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, used_at) VALUES ('%s','images')" % uuid
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ #insert in table image
+ keys = ",".join(image_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", image_dict.values() ) )
+ cmd = "INSERT INTO images (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #if result != 1: return -1, "Database Error while inserting at images table"
+
+ #insert tenants_images
+ if tenant_id != 'any':
+ cmd = "INSERT INTO tenants_images (tenant_id,image_id) VALUES ('%s','%s')" % (tenant_id, uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid, tenant_id, description) VALUES ('images','debug','%s','%s',\"new image: %s path: %s\")" % (uuid, tenant_id, image_dict['name'], image_dict['path'])
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+
+ #inseted ok
+ return 1, uuid
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "new_image", cmd, "update", tenant_id)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def delete_image_flavor(self, item_type, item_id, tenant_id):
+ '''deletes an image or flavor from database
+ item_type must be a 'image' or 'flavor'
+ item_id is the uuid
+ tenant_id is the asociated tenant, can be 'any' with means all
+ If tenan_id is not any, it deletes from tenants_images/flavors,
+ which means this image/flavor is used by this tenant, and if success,
+ it tries to delete from images/flavors in case this is not public,
+ that only will success if image is private and not used by other tenants
+ If tenant_id is any, it tries to delete from both tables at the same transaction
+ so that image/flavor is completely deleted from all tenants or nothing
+ '''
+ for retry_ in range(0,2):
+ deleted = -1
+ deleted_item = -1
+ result = (-HTTP_Internal_Server_Error, "internal error")
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+ cmd = "DELETE FROM tenants_%ss WHERE %s_id = '%s'" % (item_type, item_type, item_id)
+ if tenant_id != 'any':
+ cmd += " AND tenant_id = '%s'" % tenant_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ deleted = self.cur.rowcount
+ if tenant_id == 'any': #delete from images/flavors in the SAME transaction
+ cmd = "DELETE FROM %ss WHERE uuid = '%s'" % (item_type, item_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ deleted = self.cur.rowcount
+ if deleted>=1:
+ #delete uuid
+ cmd = "DELETE FROM uuids WHERE uuid = '%s'" % item_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,tenant_id,description) \
+ # VALUES ('%ss','debug','%s','%s','delete %s completely')" % \
+ # (item_type, item_id, tenant_id, item_type)
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+ return deleted, "%s '%s' completely deleted" % (item_type, item_id)
+ return 0, "%s '%s' not found" % (item_type, item_id)
+
+ if deleted == 1:
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,tenant_id,description) \
+ # VALUES ('%ss','debug','%s','%s','delete %s reference for this tenant')" % \
+ # (item_type, item_id, tenant_id, item_type)
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+
+ #commit transaction
+ self.cur.close()
+ #if tenant!=any delete from images/flavors in OTHER transaction. If fails is because dependencies so that not return error
+ if deleted==1:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ #delete image/flavor if not public
+ cmd = "DELETE FROM %ss WHERE uuid = '%s' AND public = 'no'" % (item_type, item_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ deleted_item = self.cur.rowcount
+ if deleted_item == 1:
+ #delete uuid
+ cmd = "DELETE FROM uuids WHERE uuid = '%s'" % item_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,tenant_id,description) \
+ # VALUES ('%ss','debug','%s','%s','delete %s completely')" % \
+ # (item_type, item_id, tenant_id, item_type)
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+ except (mdb.Error, AttributeError) as e:
+ #print "delete_%s DB Exception %d: %s" % (item_type, e.args[0], e.args[1])
+ if deleted <0:
+ result = self.format_error(e, "delete_"+item_type, cmd, "delete", "servers")
+ finally:
+ if deleted==1:
+ return 1, "%s '%s' from tenant '%s' %sdeleted" % \
+ (item_type, item_id, tenant_id, "completely " if deleted_item==1 else "")
+ elif deleted==0:
+ return 0, "%s '%s' from tenant '%s' not found" % (item_type, item_id, tenant_id)
+ else:
+ if result[0]!=-HTTP_Request_Timeout or retry_==1: return result
+
+ def delete_row(self, table, uuid):
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ #delete host
+ self.cur = self.con.cursor()
+ cmd = "DELETE FROM %s WHERE uuid = '%s'" % (table, uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ deleted = self.cur.rowcount
+ if deleted == 1:
+ #delete uuid
+ if table == 'tenants': tenant_str=uuid
+ else: tenant_str='Null'
+ self.cur = self.con.cursor()
+ cmd = "DELETE FROM uuids WHERE uuid = '%s'" % uuid
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,tenant_id,description) VALUES ('%s','debug','%s','%s','delete %s')" % (table, uuid, tenant_str, table[:-1])
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+ return deleted, table[:-1] + " '%s' %s" %(uuid, "deleted" if deleted==1 else "not found")
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "delete_row", cmd, "delete", 'instances' if table=='hosts' or table=='tenants' else 'dependencies')
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def delete_row_by_key(self, table, key, value):
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ #delete host
+ self.cur = self.con.cursor()
+ cmd = "DELETE FROM %s" % (table)
+ if key!=None:
+ if value!=None:
+ cmd += " WHERE %s = '%s'" % (key, value)
+ else:
+ cmd += " WHERE %s is null" % (key)
+ else: #delete all
+ pass
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ deleted = self.cur.rowcount
+ if deleted < 1:
+ return -1, 'Not found'
+ #delete uuid
+ return 0, deleted
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "delete_row_by_key", cmd, "delete", 'instances' if table=='hosts' or table=='tenants' else 'dependencies')
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def delete_row_by_dict(self, **sql_dict):
+ ''' Deletes rows from a table.
+ Attribute sql_dir: dictionary with the following key: value
+ 'FROM': string of table name (Mandatory)
+ 'WHERE': dict of key:values, translated to key=value AND ... (Optional)
+ 'WHERE_NOT': dict of key:values, translated to key<>value AND ... (Optional)
+ 'WHERE_NOTNULL': (list or tuple of items that must not be null in a where ... (Optional)
+ 'LIMIT': limit of number of rows (Optional)
+ Return: the (number of items deleted, descriptive test) if ok; (negative, descriptive text) if error
+ '''
+ #print sql_dict
+ from_ = "FROM " + str(sql_dict['FROM'])
+ #print 'from_', from_
+ if 'WHERE' in sql_dict and len(sql_dict['WHERE']) > 0:
+ w=sql_dict['WHERE']
+ where_ = "WHERE " + " AND ".join(map( lambda x: str(x) + (" is Null" if w[x] is None else "='"+str(w[x])+"'"), w.keys()) )
+ else: where_ = ""
+ if 'WHERE_NOT' in sql_dict and len(sql_dict['WHERE_NOT']) > 0:
+ w=sql_dict['WHERE_NOT']
+ where_2 = " AND ".join(map( lambda x: str(x) + (" is not Null" if w[x] is None else "<>'"+str(w[x])+"'"), w.keys()) )
+ if len(where_)==0: where_ = "WHERE " + where_2
+ else: where_ = where_ + " AND " + where_2
+ if 'WHERE_NOTNULL' in sql_dict and len(sql_dict['WHERE_NOTNULL']) > 0:
+ w=sql_dict['WHERE_NOTNULL']
+ where_2 = " AND ".join(map( lambda x: str(x) + " is not Null", w) )
+ if len(where_)==0: where_ = "WHERE " + where_2
+ else: where_ = where_ + " AND " + where_2
+ #print 'where_', where_
+ limit_ = "LIMIT " + str(sql_dict['LIMIT']) if 'LIMIT' in sql_dict else ""
+ #print 'limit_', limit_
+ cmd = " ".join( ("DELETE", from_, where_, limit_) )
+ self.logger.debug(cmd)
+ for retry_ in range(0,2):
+ try:
+ with self.con:
+ #delete host
+ self.cur = self.con.cursor()
+ self.cur.execute(cmd)
+ deleted = self.cur.rowcount
+ return deleted, "%d deleted from %s" % (deleted, sql_dict['FROM'][:-1] )
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "delete_row_by_dict", cmd, "delete", 'dependencies')
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+
+ def get_instance(self, instance_id):
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ #get INSTANCE
+ cmd = "SELECT uuid, name, description, progress, host_id, flavor_id, image_id, status, last_error, tenant_id, ram, vcpus, created_at \
+ FROM instances WHERE uuid = '" + str(instance_id) +"'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount == 0 : return 0, "instance '" + str(instance_id) +"'not found."
+ instance = self.cur.fetchone()
+ #get networks
+ cmd = "SELECT uuid as iface_id, net_id, mac as mac_address, ip_address, name, Mbps as bandwidth, vpci, model \
+ FROM ports WHERE type = 'instance:bridge' AND instance_id = '" + instance_id + "'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount > 0 :
+ instance['networks'] = self.cur.fetchall()
+
+ #get extended
+ extended = {}
+ #get devices
+ cmd = "SELECT type, vpci, image_id, xml,dev FROM instance_devices WHERE instance_id = '%s' " % str(instance_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount > 0 :
+ extended['devices'] = self.cur.fetchall()
+ #get numas
+ numas = []
+ cmd = "SELECT id, numa_socket as source FROM numas WHERE host_id = '" + str(instance['host_id']) + "'"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ host_numas = self.cur.fetchall()
+ #print 'host_numas', host_numas
+ for k in host_numas:
+ numa_id = str(k['id'])
+ numa_dict ={}
+ #get memory
+ cmd = "SELECT consumed FROM resources_mem WHERE instance_id = '%s' AND numa_id = '%s'" % ( instance_id, numa_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount > 0:
+ mem_dict = self.cur.fetchone()
+ numa_dict['memory'] = mem_dict['consumed']
+ #get full cores
+ cursor2 = self.con.cursor()
+ cmd = "SELECT core_id, paired, MIN(v_thread_id) as v1, MAX(v_thread_id) as v2, COUNT(instance_id) as nb, MIN(thread_id) as t1, MAX(thread_id) as t2 FROM resources_core WHERE instance_id = '%s' AND numa_id = '%s' GROUP BY core_id,paired" % ( str(instance_id), numa_id)
+ self.logger.debug(cmd)
+ cursor2.execute(cmd)
+ core_list = []; core_source = []
+ paired_list = []; paired_source = []
+ thread_list = []; thread_source = []
+ if cursor2.rowcount > 0:
+ cores = cursor2.fetchall()
+ for core in cores:
+ if core[4] == 2: #number of used threads from core
+ if core[3] == core[2]: #only one thread asigned to VM, so completely core
+ core_list.append(core[2])
+ core_source.append(core[5])
+ elif core[1] == 'Y':
+ paired_list.append(core[2:4])
+ paired_source.append(core[5:7])
+ else:
+ thread_list.extend(core[2:4])
+ thread_source.extend(core[5:7])
+
+ else:
+ thread_list.append(core[2])
+ thread_source.append(core[5])
+ if len(core_list) > 0:
+ numa_dict['cores'] = len(core_list)
+ numa_dict['cores-id'] = core_list
+ numa_dict['cores-source'] = core_source
+ if len(paired_list) > 0:
+ numa_dict['paired-threads'] = len(paired_list)
+ numa_dict['paired-threads-id'] = paired_list
+ numa_dict['paired-threads-source'] = paired_source
+ if len(thread_list) > 0:
+ numa_dict['threads'] = len(thread_list)
+ numa_dict['threads-id'] = thread_list
+ numa_dict['threads-source'] = thread_source
+
+ #get dedicated ports and SRIOV
+ cmd = "SELECT port_id as iface_id, p.vlan as vlan, p.mac as mac_address, net_id, if(model='PF','yes',if(model='VF','no','yes:sriov')) as dedicated,\
+ rp.Mbps as bandwidth, name, vpci, pci as source \
+ FROM resources_port as rp join ports as p on port_id=uuid WHERE p.instance_id = '%s' AND numa_id = '%s' and p.type='instance:data'" % (instance_id, numa_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount > 0:
+ numa_dict['interfaces'] = self.cur.fetchall()
+ #print 'interfaces', numa_dict
+
+ if len(numa_dict) > 0 :
+ numa_dict['source'] = k['source'] #numa socket
+ numas.append(numa_dict)
+
+ if len(numas) > 0 : extended['numas'] = numas
+ if len(extended) > 0 : instance['extended'] = extended
+ af.DeleteNone(instance)
+ return 1, instance
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "get_instance", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def get_numas(self, requirements, prefered_host_id=None, only_of_ports=True):
+ '''Obtain a valid NUMA/HOST for deployment a VM
+ requirements: contain requirement regarding:
+ requirements['ram']: Non huge page memory in MB; 0 to skip
+ requirements['vcpus']: Non isolated cpus; 0 to skip
+ requirements['numa']: Requiremets to be fixed in ONE Numa node
+ requirements['numa']['memory']: Huge page memory in GB at ; 0 for any
+ requirements['numa']['proc_req_type']: Type of processor, cores or threads
+ requirements['numa']['proc_req_nb']: Number of isolated cpus
+ requirements['numa']['port_list']: Physical NIC ports list ; [] for any
+ requirements['numa']['sriov_list']: Virtual function NIC ports list ; [] for any
+ prefered_host_id: if not None return this host if it match
+ only_of_ports: if True only those ports conected to the openflow (of) are valid,
+ that is, with switch_port information filled; if False, all NIC ports are valid.
+ Return a valid numa and host
+ '''
+
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+# #Find numas of prefered host
+# prefered_numas = ()
+# if prefered_host_id != None:
+# self.cur = self.con.cursor()
+# self.cur.execute("SELECT id FROM numas WHERE host_id='%s'" + prefered_host_id)
+# prefered_numas = self.cur.fetchall()
+# self.cur.close()
+
+ #Find valid host for the ram and vcpus
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ cmd = "CALL GetHostByMemCpu(%s, %s)" % (str(requirements['ram']), str(requirements['vcpus']))
+ self.logger.debug(cmd)
+ self.cur.callproc('GetHostByMemCpu', (str(requirements['ram']), str(requirements['vcpus'])) )
+ valid_hosts = self.cur.fetchall()
+ self.cur.close()
+ self.cur = self.con.cursor()
+ match_found = False
+ if len(valid_hosts)<=0:
+ error_text = 'No room at data center. Can not find a host with %s MB memory and %s cpus available' % (str(requirements['ram']), str(requirements['vcpus']))
+ #self.logger.debug(error_text)
+ return -1, error_text
+
+ #elif req_numa != None:
+ #Find valid numa nodes for memory requirements
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ cmd = "CALL GetNumaByMemory(%s)" % str(requirements['numa']['memory'])
+ self.logger.debug(cmd)
+ self.cur.callproc('GetNumaByMemory', (requirements['numa']['memory'],) )
+ valid_for_memory = self.cur.fetchall()
+ self.cur.close()
+ self.cur = self.con.cursor()
+ if len(valid_for_memory)<=0:
+ error_text = 'No room at data center. Can not find a host with %s GB Hugepages memory available' % str(requirements['numa']['memory'])
+ #self.logger.debug(error_text)
+ return -1, error_text
+
+ #Find valid numa nodes for processor requirements
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ if requirements['numa']['proc_req_type'] == 'threads':
+ cpu_requirement_text='cpu-threads'
+ cmd = "CALL GetNumaByThread(%s)" % str(requirements['numa']['proc_req_nb'])
+ self.logger.debug(cmd)
+ self.cur.callproc('GetNumaByThread', (requirements['numa']['proc_req_nb'],) )
+ else:
+ cpu_requirement_text='cpu-cores'
+ cmd = "CALL GetNumaByCore(%s)" % str(requirements['numa']['proc_req_nb'])
+ self.logger.debug(cmd)
+ self.cur.callproc('GetNumaByCore', (requirements['numa']['proc_req_nb'],) )
+ valid_for_processor = self.cur.fetchall()
+ self.cur.close()
+ self.cur = self.con.cursor()
+ if len(valid_for_processor)<=0:
+ error_text = 'No room at data center. Can not find a host with %s %s available' % (str(requirements['numa']['proc_req_nb']),cpu_requirement_text)
+ #self.logger.debug(error_text)
+ return -1, error_text
+
+ #Find the numa nodes that comply for memory and processor requirements
+ #sorting from less to more memory capacity
+ valid_numas = []
+ for m_numa in valid_for_memory:
+ numa_valid_for_processor = False
+ for p_numa in valid_for_processor:
+ if m_numa['numa_id'] == p_numa['numa_id']:
+ numa_valid_for_processor = True
+ break
+ numa_valid_for_host = False
+ prefered_numa = False
+ for p_host in valid_hosts:
+ if m_numa['host_id'] == p_host['uuid']:
+ numa_valid_for_host = True
+ if p_host['uuid'] == prefered_host_id:
+ prefered_numa = True
+ break
+ if numa_valid_for_host and numa_valid_for_processor:
+ if prefered_numa:
+ valid_numas.insert(0, m_numa['numa_id'])
+ else:
+ valid_numas.append(m_numa['numa_id'])
+ if len(valid_numas)<=0:
+ error_text = 'No room at data center. Can not find a host with %s MB hugepages memory and %s %s available in the same numa' %\
+ (requirements['numa']['memory'], str(requirements['numa']['proc_req_nb']),cpu_requirement_text)
+ #self.logger.debug(error_text)
+ return -1, error_text
+
+ # print 'Valid numas list: '+str(valid_numas)
+
+ #Find valid numa nodes for interfaces requirements
+ #For each valid numa we will obtain the number of available ports and check if these are valid
+ match_found = False
+ for numa_id in valid_numas:
+ # print 'Checking '+str(numa_id)
+ match_found = False
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ if only_of_ports:
+ cmd="CALL GetAvailablePorts(%s)" % str(numa_id)
+ self.logger.debug(cmd)
+ self.cur.callproc('GetAvailablePorts', (numa_id,) )
+ else:
+ cmd="CALL GetAllAvailablePorts(%s)" % str(numa_id)
+ self.logger.debug(cmd)
+ self.cur.callproc('GetAllAvailablePorts', (numa_id,) )
+ available_ports = self.cur.fetchall()
+ self.cur.close()
+ self.cur = self.con.cursor()
+
+ #Set/reset reservations
+ for port in available_ports:
+ port['Mbps_reserved'] = 0
+ port['SRIOV_reserved'] = 0
+
+ #Try to allocate physical ports
+ physical_ports_found = True
+ for iface in requirements['numa']['port_list']:
+ # print '\t\tchecking iface: '+str(iface)
+ portFound = False
+ for port in available_ports:
+ # print '\t\t\tfor port: '+str(port)
+ #If the port is not empty continue
+ if port['Mbps_free'] != port['Mbps'] or port['Mbps_reserved'] != 0:
+ # print '\t\t\t\t Not empty port'
+ continue;
+ #If the port speed is not enough continue
+ if port['Mbps'] < iface['bandwidth']:
+ # print '\t\t\t\t Not enough speed'
+ continue;
+
+ #Otherwise this is a valid port
+ port['Mbps_reserved'] = port['Mbps']
+ port['SRIOV_reserved'] = 0
+ iface['port_id'] = port['port_id']
+ iface['vlan'] = None
+ iface['mac'] = port['mac']
+ iface['switch_port'] = port['switch_port']
+ # print '\t\t\t\t Dedicated port found '+str(port['port_id'])
+ portFound = True
+ break;
+
+ #if all ports have been checked and no match has been found
+ #this is not a valid numa
+ if not portFound:
+ # print '\t\t\t\t\tAll ports have been checked and no match has been found for numa '+str(numa_id)+'\n\n'
+ physical_ports_found = False
+ break
+
+ #if there is no match continue checking the following numa
+ if not physical_ports_found:
+ continue
+
+ #Try to allocate SR-IOVs
+ sriov_ports_found = True
+ for iface in requirements['numa']['sriov_list']:
+ # print '\t\tchecking iface: '+str(iface)
+ portFound = False
+ for port in available_ports:
+ # print '\t\t\tfor port: '+str(port)
+ #If there are not available SR-IOVs continue
+ if port['availableSRIOV'] - port['SRIOV_reserved'] <= 0:
+ # print '\t\t\t\t Not enough SR-IOV'
+ continue;
+ #If the port free speed is not enough continue
+ if port['Mbps_free'] - port['Mbps_reserved'] < iface['bandwidth']:
+ # print '\t\t\t\t Not enough speed'
+ continue;
+
+ #Otherwise this is a valid port
+ port['Mbps_reserved'] += iface['bandwidth']
+ port['SRIOV_reserved'] += 1
+ # print '\t\t\t\t SR-IOV found '+str(port['port_id'])
+ iface['port_id'] = port['port_id']
+ iface['vlan'] = None
+ iface['mac'] = port['mac']
+ iface['switch_port'] = port['switch_port']
+ portFound = True
+ break;
+
+ #if all ports have been checked and no match has been found
+ #this is not a valid numa
+ if not portFound:
+ # print '\t\t\t\t\tAll ports have been checked and no match has been found for numa '+str(numa_id)+'\n\n'
+ sriov_ports_found = False
+ break
+
+ #if there is no match continue checking the following numa
+ if not sriov_ports_found:
+ continue
+
+
+ if sriov_ports_found and physical_ports_found:
+ match_found = True
+ break
+
+ if not match_found:
+ error_text = 'No room at data center. Can not find a host with the required hugepages, vcpus and interfaces'
+ #self.logger.debug(error_text)
+ return -1, error_text
+
+ #self.logger.debug('Full match found in numa %s', str(numa_id))
+
+ for numa in valid_for_processor:
+ if numa_id==numa['numa_id']:
+ host_id=numa['host_id']
+ break
+ return 0, {'numa_id':numa_id, 'host_id': host_id, }
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "get_numas", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def new_instance(self, instance_dict, nets, ports_to_free):
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+
+ #create uuid if not provided
+ if 'uuid' not in instance_dict:
+ uuid = instance_dict['uuid'] = str(myUuid.uuid1()) # create_uuid
+ else: #check uuid is valid
+ uuid = str(instance_dict['uuid'])
+
+
+ #inserting new uuid
+ cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'instances')" % (uuid, uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ #insert in table instance
+ extended = instance_dict.pop('extended', None);
+ bridgedifaces = instance_dict.pop('bridged-ifaces', () );
+
+ keys = ",".join(instance_dict.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", instance_dict.values() ) )
+ cmd = "INSERT INTO instances (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #if result != 1: return -1, "Database Error while inserting at instances table"
+
+ #insert resources
+ nb_bridge_ifaces = nb_cores = nb_ifaces = nb_numas = 0
+ #insert bridged_ifaces
+ for iface in bridgedifaces:
+ #generate and insert a iface uuid
+ iface['uuid'] = str(myUuid.uuid1()) # create_uuid
+ cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'ports')" % (iface['uuid'], uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #insert iface
+ iface['instance_id'] = uuid
+ iface['type'] = 'instance:bridge'
+ if 'name' not in iface: iface['name']="br"+str(nb_bridge_ifaces)
+ iface['Mbps']=iface.pop('bandwidth', None)
+ if 'mac_address' not in iface:
+ iface['mac'] = af.gen_random_mac()
+ else:
+ iface['mac'] = iface['mac_address']
+ del iface['mac_address']
+ #iface['mac']=iface.pop('mac_address', None) #for leaving mac generation to libvirt
+ keys = ",".join(iface.keys())
+ values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", iface.values() ) )
+ cmd = "INSERT INTO ports (" + keys + ") VALUES (" + values + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ nb_bridge_ifaces += 1
+
+ if extended is not None:
+ if 'numas' not in extended or extended['numas'] is None: extended['numas'] = ()
+ for numa in extended['numas']:
+ nb_numas += 1
+ #cores
+ if 'cores' not in numa or numa['cores'] is None: numa['cores'] = ()
+ for core in numa['cores']:
+ nb_cores += 1
+ cmd = "UPDATE resources_core SET instance_id='%s'%s%s WHERE id='%s'" \
+ % (uuid, \
+ (",v_thread_id='" + str(core['vthread']) + "'") if 'vthread' in core else '', \
+ (",paired='" + core['paired'] + "'") if 'paired' in core else '', \
+ core['id'] )
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #interfaces
+ if 'interfaces' not in numa or numa['interfaces'] is None: numa['interfaces'] = ()
+ for iface in numa['interfaces']:
+ #generate and insert an uuid; iface[id]=iface_uuid; iface[uuid]= net_id
+ iface['id'] = str(myUuid.uuid1()) # create_uuid
+ cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'ports')" % (iface['id'], uuid)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ nb_ifaces += 1
+ mbps_=("'"+str(iface['Mbps_used'])+"'") if 'Mbps_used' in iface and iface['Mbps_used'] is not None else "Mbps"
+ if iface["dedicated"]=="yes":
+ iface_model="PF"
+ elif iface["dedicated"]=="yes:sriov":
+ iface_model="VFnotShared"
+ elif iface["dedicated"]=="no":
+ iface_model="VF"
+ #else error
+ INSERT=(iface['mac_address'], iface['switch_port'], iface.get('vlan',None), 'instance:data', iface['Mbps_used'], iface['id'],
+ uuid, instance_dict['tenant_id'], iface.get('name',None), iface.get('vpci',None), iface.get('uuid',None), iface_model )
+ cmd = "INSERT INTO ports (mac,switch_port,vlan,type,Mbps,uuid,instance_id,tenant_id,name,vpci,net_id, model) " + \
+ " VALUES (" + ",".join(map(lambda x: 'Null' if x is None else "'"+str(x)+"'", INSERT )) + ")"
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if 'uuid' in iface:
+ nets.append(iface['uuid'])
+
+ #discover if this port is not used by anyone
+ cmd = "SELECT source_name, mac FROM ( SELECT root_id, count(instance_id) as used FROM resources_port" \
+ " WHERE root_id=(SELECT root_id from resources_port WHERE id='%s')"\
+ " GROUP BY root_id ) AS A JOIN resources_port as B ON A.root_id=B.id AND A.used=0" % iface['port_id']
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ports_to_free += self.cur.fetchall()
+
+ cmd = "UPDATE resources_port SET instance_id='%s', port_id='%s',Mbps_used=%s WHERE id='%s'" \
+ % (uuid, iface['id'], mbps_, iface['port_id'])
+ #if Mbps_used not suply, set the same value of 'Mpbs', that is the total
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ #memory
+ if 'memory' in numa and numa['memory'] is not None and numa['memory']>0:
+ cmd = "INSERT INTO resources_mem (numa_id, instance_id, consumed) VALUES ('%s','%s','%s')" % (numa['numa_id'], uuid, numa['memory'])
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if 'devices' not in extended or extended['devices'] is None: extended['devices'] = ()
+ for device in extended['devices']:
+ if 'vpci' in device: vpci = "'" + device['vpci'] + "'"
+ else: vpci = 'Null'
+ if 'image_id' in device: image_id = "'" + device['image_id'] + "'"
+ else: image_id = 'Null'
+ if 'xml' in device: xml = "'" + device['xml'] + "'"
+ else: xml = 'Null'
+ if 'dev' in device: dev = "'" + device['dev'] + "'"
+ else: dev = 'Null'
+ cmd = "INSERT INTO instance_devices (type, instance_id, image_id, vpci, xml, dev) VALUES ('%s','%s', %s, %s, %s, %s)" % \
+ (device['type'], uuid, image_id, vpci, xml, dev)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ##inserting new log
+ #cmd = "INSERT INTO logs (related,level,uuid,description) VALUES ('instances','debug','%s','new instance: %d numas, %d theads, %d ifaces %d bridge_ifaces')" % (uuid, nb_numas, nb_cores, nb_ifaces, nb_bridge_ifaces)
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+
+ #inseted ok
+ return 1, uuid
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "new_instance", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def delete_instance(self, instance_id, tenant_id, net_list, ports_to_free, logcause="requested by http"):
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor()
+ #get INSTANCE
+ cmd = "SELECT uuid FROM instances WHERE uuid='%s' AND tenant_id='%s'" % (instance_id, tenant_id)
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount == 0 : return 0, "instance %s not found in tenant %s" % (instance_id, tenant_id)
+
+ #delete bridged ifaces, instace_devices, resources_mem; done by database: it is automatic by Database; FOREIGN KEY DELETE CASCADE
+
+ #get nets afected
+ cmd = "SELECT DISTINCT net_id from ports WHERE instance_id = '%s' AND net_id is not Null AND type='instance:data'" % instance_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ net_list__ = self.cur.fetchall()
+ for net in net_list__:
+ net_list.append(net[0])
+
+ #get dataplane interfaces releases by this VM; both PF and VF with no other VF
+ cmd="SELECT source_name, mac FROM (SELECT root_id, count(instance_id) as used FROM resources_port WHERE instance_id='%s' GROUP BY root_id ) AS A" % instance_id \
+ + " JOIN (SELECT root_id, count(instance_id) as used FROM resources_port GROUP BY root_id) AS B ON A.root_id=B.root_id AND A.used=B.used"\
+ + " JOIN resources_port as C ON A.root_id=C.id"
+# cmd = "SELECT DISTINCT root_id FROM resources_port WHERE instance_id = '%s'" % instance_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ports_to_free += self.cur.fetchall()
+
+ #update resources port
+ cmd = "UPDATE resources_port SET instance_id=Null, port_id=Null, Mbps_used='0' WHERE instance_id = '%s'" % instance_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+# #filter dataplane ports used by this VM that now are free
+# for port in ports_list__:
+# cmd = "SELECT mac, count(instance_id) FROM resources_port WHERE root_id = '%s'" % port[0]
+# self.logger.debug(cmd)
+# self.cur.execute(cmd)
+# mac_list__ = self.cur.fetchone()
+# if mac_list__ and mac_list__[1]==0:
+# ports_to_free.append(mac_list__[0])
+
+
+ #update resources core
+ cmd = "UPDATE resources_core SET instance_id=Null, v_thread_id=Null, paired='N' WHERE instance_id = '%s'" % instance_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ #delete all related uuids
+ cmd = "DELETE FROM uuids WHERE root_uuid='%s'" % instance_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+
+ ##insert log
+ #cmd = "INSERT INTO logs (related,level,uuid,description) VALUES ('instances','debug','%s','delete instance %s')" % (instance_id, logcause)
+ #self.logger.debug(cmd)
+ #self.cur.execute(cmd)
+
+ #delete instance
+ cmd = "DELETE FROM instances WHERE uuid='%s' AND tenant_id='%s'" % (instance_id, tenant_id)
+ self.cur.execute(cmd)
+ return 1, "instance %s from tenant %s DELETED" % (instance_id, tenant_id)
+
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "delete_instance", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def get_ports(self, WHERE):
+ ''' Obtain ports using the WHERE filtering.
+ Attributes:
+ 'where_': dict of key:values, translated to key=value AND ... (Optional)
+ Return: a list with dictionarys at each row
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ select_ = "SELECT uuid,'ACTIVE' as status,admin_state_up,name,net_id,\
+ tenant_id,type,mac,vlan,switch_port,instance_id,Mbps FROM ports "
+
+ if WHERE is None or len(WHERE) == 0: where_ = ""
+ else:
+ where_ = "WHERE " + " AND ".join(map( lambda x: str(x) + (" is Null" if WHERE[x] is None else "='"+str(WHERE[x])+"'"), WHERE.keys()) )
+ limit_ = "LIMIT 100"
+ cmd = " ".join( (select_, where_, limit_) )
+ # print "SELECT multiple de instance_ifaces, iface_uuid, external_ports" #print cmd
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ ports = self.cur.fetchall()
+ if self.cur.rowcount>0: af.DeleteNone(ports)
+ return self.cur.rowcount, ports
+ # return self.get_table(FROM=from_, SELECT=select_,WHERE=where_,LIMIT=100)
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "get_ports", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+
+ def check_target_net(self, net_id, tenant_id, port_type):
+ '''check if valid attachement of a port into a target net
+ Attributes:
+ net_id: target net uuid
+ tenant_id: client where tenant belongs. Not used in this version
+ port_type: string with the option 'instance:bridge', 'instance:data', 'external'
+ Return:
+ (0,net_dict) if ok, where net_dict contain 'uuid','type','vlan', ...
+ (negative,string-error) if error
+ '''
+ for retry_ in range(0,2):
+ cmd=""
+ try:
+ with self.con:
+ self.cur = self.con.cursor(mdb.cursors.DictCursor)
+ cmd = "SELECT * FROM nets WHERE uuid='%s'" % net_id
+ self.logger.debug(cmd)
+ self.cur.execute(cmd)
+ if self.cur.rowcount == 0 : return -1, "network_id %s does not match any net" % net_id
+ net = self.cur.fetchone()
+ break
+
+ except (mdb.Error, AttributeError) as e:
+ r,c = self.format_error(e, "check_target_net", cmd)
+ if r!=-HTTP_Request_Timeout or retry_==1: return r,c
+ #check permissions
+ if tenant_id is not None and tenant_id is not "admin":
+ if net['tenant_id']==tenant_id and net['shared']=='false':
+ return -1, "needed admin privileges to attach to the net %s" % net_id
+ #check types
+ if (net['type'] in ('p2p','data') and 'port_type' == 'instance:bridge') or \
+ (net['type'] in ('bridge_data','bridge_man') and 'port_type' != 'instance:bridge') :
+ return -1, "can not attach a port of type %s into a net of type %s" % (port_type, net['type'])
+ if net['type'] == 'ptp':
+ #look how many
+ nb_ports, data = self.get_ports( {'net_id':net_id} )
+ if nb_ports<0:
+ return -1, data
+ else:
+ if net['provider']:
+ nb_ports +=1
+ if nb_ports >=2:
+ return -1, "net of type p2p already contain two ports attached. No room for another"
+
+ return 0, net
+
+if __name__ == "__main__":
+ print "Hello World"
--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+''' Definition of dictionaries schemas used by validating input
+ These dictionaries are validated using jsonschema library
+'''
+__author__="Alfonso Tierno"
+__date__ ="$10-jul-2014 12:07:15$"
+
+#
+# SCHEMAS to validate input data
+#
+
+path_schema={"type":"string", "pattern":"^(\.){0,2}(/[^/\"':{}\(\)]+)+$"}
+http_schema={"type":"string", "pattern":"^https?://[^'\"=]+$"}
+port_schema={"type":"integer","minimum":1,"maximun":65534}
+ip_schema={"type":"string","pattern":"^([0-9]{1,3}.){3}[0-9]{1,3}$"}
+cidr_schema={"type":"string","pattern":"^([0-9]{1,3}.){3}[0-9]{1,3}/[0-9]{1,2}$"}
+name_schema={"type" : "string", "minLength":1, "maxLength":255, "pattern" : "^[^,;()'\"]+$"}
+nameshort_schema={"type" : "string", "minLength":1, "maxLength":64, "pattern" : "^[^,;()'\"]+$"}
+nametiny_schema={"type" : "string", "minLength":1, "maxLength":12, "pattern" : "^[^,;()'\"]+$"}
+xml_text_schema={"type" : "string", "minLength":1, "maxLength":1000, "pattern" : "^[^']+$"}
+description_schema={"type" : ["string","null"], "maxLength":255, "pattern" : "^[^'\"]+$"}
+id_schema_fake = {"type" : "string", "minLength":2, "maxLength":36 } #"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
+pci_schema={"type":"string", "pattern":"^[0-9a-fA-F]{4}(:[0-9a-fA-F]{2}){2}\.[0-9a-fA-F]$"}
+bandwidth_schema={"type":"string", "pattern" : "^[0-9]+ *([MG]bps)?$"}
+integer0_schema={"type":"integer","minimum":0}
+integer1_schema={"type":"integer","minimum":1}
+vlan_schema={"type":"integer","minimum":1,"maximun":4095}
+vlan1000_schema={"type":"integer","minimum":1000,"maximun":4095}
+mac_schema={"type":"string", "pattern":"^[0-9a-fA-F][02468aceACE](:[0-9a-fA-F]{2}){5}$"} #must be unicast LSB bit of MSB byte ==0
+net_bind_schema={"oneOf":[{"type":"null"},{"type":"string", "pattern":"^(default|((bridge|macvtap):[0-9a-zA-Z\.\-]{1,50})|openflow:[/0-9a-zA-Z\.\-]{1,50}(:vlan)?)$"}]}
+yes_no_schema={"type":"string", "enum":["yes", "no"]}
+log_level_schema={"type":"string", "enum":["DEBUG", "INFO", "WARNING","ERROR","CRITICAL"]}
+
+config_schema = {
+ "title":"main configuration information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "http_port": port_schema,
+ "http_admin_port": port_schema,
+ "http_host": nameshort_schema,
+ "http_url_prefix": path_schema, # it does not work yet; it's supposed to be the base path to be used by bottle, but it must be explicitly declared
+ "db_host": nameshort_schema,
+ "db_user": nameshort_schema,
+ "db_passwd": {"type":"string"},
+ "db_name": nameshort_schema,
+ "of_controller_ip": ip_schema,
+ "of_controller_port": port_schema,
+ "of_controller_dpid": nameshort_schema,
+ "of_controller_nets_with_same_vlan": {"type" : "boolean"},
+ "of_controller": nameshort_schema, #{"type":"string", "enum":["floodlight", "opendaylight"]},
+ "of_controller_module": {"type":"string"},
+ #"of_user": nameshort_schema,
+ #"of_password": nameshort_schema,
+ "test_mode": {"type": "boolean"}, #leave for backward compatibility
+ "mode": {"type":"string", "enum":["normal", "host only", "OF only", "development", "test"] },
+ "development_bridge": {"type":"string"},
+ "tenant_id": {"type" : "string"},
+ "image_path": path_schema,
+ "network_vlan_range_start": vlan_schema,
+ "network_vlan_range_end": vlan_schema,
+ "bridge_ifaces": {
+ "type": "object",
+ "patternProperties": {
+ "." : {
+ "type": "array",
+ "items": integer0_schema,
+ "minItems":2,
+ "maxItems":2,
+ },
+ },
+ "minProperties": 2
+ },
+ "dhcp_server": {
+ "type": "object",
+ "properties": {
+ "host" : name_schema,
+ "port" : port_schema,
+ "provider" : {"type": "string", "enum": ["isc-dhcp-server"]},
+ "user" : nameshort_schema,
+ "password" : {"type": "string"},
+ "key" : {"type": "string"},
+ "bridge_ifaces" :{
+ "type": "array",
+ "items": nameshort_schema,
+ },
+ "nets" :{
+ "type": "array",
+ "items": name_schema,
+ },
+ },
+ "required": ['host', 'provider', 'user']
+ },
+ "log_level": log_level_schema,
+ "log_level_db": log_level_schema,
+ "log_level_of": log_level_schema,
+ },
+ "patternProperties": {
+ "of_*" : {"type": ["string", "integer", "boolean"]}
+ },
+ "required": ['db_host', 'db_user', 'db_passwd', 'db_name',
+ 'of_controller_ip', 'of_controller_port', 'of_controller_dpid', 'bridge_ifaces', 'of_controller'],
+ "additionalProperties": False
+}
+
+
+
+metadata_schema={
+ "type":"object",
+ "properties":{
+ "architecture": {"type":"string"},
+ "use_incremental": yes_no_schema,
+ "vpci": pci_schema,
+ "os_distro": {"type":"string"},
+ "os_type": {"type":"string"},
+ "os_version": {"type":"string"},
+ "bus": {"type":"string"},
+ "topology": {"type":"string", "enum": ["oneSocket"]}
+ }
+}
+
+
+
+tenant_new_schema = {
+ "title":"tenant creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "tenant":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "name": nameshort_schema,
+ "description":description_schema,
+ "enabled":{"type" : "boolean"}
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["tenant"],
+ "additionalProperties": False
+}
+tenant_edit_schema = {
+ "title":"tenant edition information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "tenant":{
+ "type":"object",
+ "minProperties":1,
+ "properties":{
+ "name":nameshort_schema,
+ "description":description_schema,
+ "enabled":{"type" : "boolean"}
+ },
+ "additionalProperties": False,
+ }
+ },
+ "required": ["tenant"],
+ "additionalProperties": False
+}
+interfaces_schema={
+ "type":"array",
+ "minItems":0,
+ "items":{
+ "type":"object",
+ "properties":{
+ "name":name_schema,
+ "dedicated":{"type":"string","enum":["yes","no","yes:sriov"]},
+ "bandwidth":bandwidth_schema,
+ "vpci":pci_schema,
+ "uuid":id_schema,
+ "mac_address":mac_schema
+ },
+ "additionalProperties": False,
+ "required": ["dedicated", "bandwidth"]
+ }
+}
+
+extended_schema={
+ "type":"object",
+ "properties":{
+ "processor_ranking":integer0_schema,
+ "devices":{
+ "type": "array",
+ "items":{
+ "type": "object",
+ "properties":{
+ "type":{"type":"string", "enum":["usb","disk","cdrom","xml"]},
+ "vpci":pci_schema,
+ "imageRef":id_schema,
+ "xml":xml_text_schema,
+ "dev":nameshort_schema
+ },
+ "additionalProperties": False,
+ "required": ["type"]
+ }
+ },
+ "numas":{
+ "type": "array",
+ "items":{
+ "type": "object",
+ "properties":{
+ "memory":integer1_schema,
+ "cores":integer1_schema,
+ "paired-threads":integer1_schema,
+ "threads":integer1_schema,
+ "cores-id":{"type":"array","items":integer0_schema},
+ "paired-threads-id":{"type":"array","items":{"type":"array","minItems":2,"maxItems":2,"items":integer0_schema}},
+ "threads-id":{"type":"array","items":integer0_schema},
+ "interfaces":interfaces_schema
+ },
+ "additionalProperties": False,
+ "minProperties": 1,
+ #"required": ["memory"]
+ }
+ }
+ },
+ #"additionalProperties": False,
+ #"required": ["processor_ranking"]
+}
+
+host_data_schema={
+ "title":"hosts manual insertion information schema",
+ "type":"object",
+ "properties":{
+ "ip_name":nameshort_schema,
+ "name": name_schema,
+ "description":description_schema,
+ "user":nameshort_schema,
+ "password":nameshort_schema,
+ "features":description_schema,
+ "ranking":integer0_schema,
+ "devices":{
+ "type": "array",
+ "items":{
+ "type": "object",
+ "properties":{
+ "type":{"type":"string", "enum":["usb","disk"]},
+ "vpci":pci_schema
+ },
+ "additionalProperties": False,
+ "required": ["type"]
+ }
+ },
+ "numas":{
+ "type": "array",
+ "minItems":1,
+ "items":{
+ "type": "object",
+ "properties":{
+ "admin_state_up":{"type":"boolean"},
+ "hugepages":integer1_schema,
+ "cores":{
+ "type": "array",
+ "minItems":2,
+ "items":{
+ "type": "object",
+ "properties":{
+ "core_id":integer0_schema,
+ "thread_id":integer0_schema,
+ "status": {"type":"string", "enum":["noteligible"]}
+ },
+ "additionalProperties": False,
+ "required": ["core_id","thread_id"]
+ }
+ },
+ "interfaces":{
+ "type": "array",
+ "minItems":1,
+ "items":{
+ "type": "object",
+ "properties":{
+ "source_name":nameshort_schema,
+ "mac":mac_schema,
+ "Mbps":integer0_schema,
+ "pci":pci_schema,
+ "sriovs":{
+ "type": "array",
+ "minItems":1,
+ "items":{
+ "type": "object",
+ "properties":{
+ "source_name":{"oneOf":[integer0_schema, nameshort_schema]},
+ "mac":mac_schema,
+ "vlan":integer0_schema,
+ "pci":pci_schema,
+ },
+ "additionalProperties": False,
+ "required": ["source_name","mac","pci"]
+ }
+ },
+ "switch_port": nameshort_schema,
+ "switch_dpid": nameshort_schema,
+ },
+ "additionalProperties": False,
+ "required": ["source_name","mac","Mbps","pci"]
+ }
+ },
+ "numa_socket":integer0_schema,
+ "memory":integer1_schema
+ },
+ "additionalProperties": False,
+ "required": ["hugepages","cores","numa_socket"]
+ }
+ }
+ },
+ "additionalProperties": False,
+ "required": ["ranking", "numas","ip_name","user"]
+}
+
+host_edit_schema={
+ "title":"hosts creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "host":{
+ "type":"object",
+ "properties":{
+ "ip_name":nameshort_schema,
+ "name": name_schema,
+ "description":description_schema,
+ "user":nameshort_schema,
+ "password":nameshort_schema,
+ "admin_state_up":{"type":"boolean"},
+ "numas":{
+ "type":"array",
+ "items":{
+ "type": "object",
+ "properties":{
+ "numa_socket": integer0_schema,
+ "admin_state_up":{"type":"boolean"},
+ "interfaces":{
+ "type":"array",
+ "items":{
+ "type": "object",
+ "properties":{
+ "source_name": nameshort_schema,
+ "switch_dpid": nameshort_schema,
+ "switch_port": nameshort_schema,
+ },
+ "required": ["source_name"],
+ }
+ }
+ },
+ "required": ["numa_socket"],
+ "additionalProperties": False,
+ }
+ }
+ },
+ "minProperties": 1,
+ "additionalProperties": False
+ },
+ },
+ "required": ["host"],
+ "minProperties": 1,
+ "additionalProperties": False
+}
+
+host_new_schema = {
+ "title":"hosts creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "host":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "ip_name":nameshort_schema,
+ "name": name_schema,
+ "description":description_schema,
+ "user":nameshort_schema,
+ "password":nameshort_schema,
+ "admin_state_up":{"type":"boolean"},
+ },
+ "required": ["name","ip_name","user"]
+ },
+ "host-data":host_data_schema
+ },
+ "required": ["host"],
+ "minProperties": 1,
+ "maxProperties": 2,
+ "additionalProperties": False
+}
+
+
+flavor_new_schema = {
+ "title":"flavor creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "flavor":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "name":name_schema,
+ "description":description_schema,
+ "ram":integer0_schema,
+ "vcpus":integer0_schema,
+ "extended": extended_schema,
+ "public": yes_no_schema
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["flavor"],
+ "additionalProperties": False
+}
+flavor_update_schema = {
+ "title":"flavor update information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "flavor":{
+ "type":"object",
+ "properties":{
+ "name":name_schema,
+ "description":description_schema,
+ "ram":integer0_schema,
+ "vcpus":integer0_schema,
+ "extended": extended_schema,
+ "public": yes_no_schema
+ },
+ "minProperties": 1,
+ "additionalProperties": False
+ }
+ },
+ "required": ["flavor"],
+ "additionalProperties": False
+}
+
+image_new_schema = {
+ "title":"image creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "image":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "path": {"oneOf": [path_schema, http_schema]},
+ "description":description_schema,
+ "name":name_schema,
+ "metadata":metadata_schema,
+ "public": yes_no_schema
+ },
+ "required": ["name","path"]
+ }
+ },
+ "required": ["image"],
+ "additionalProperties": False
+}
+
+image_update_schema = {
+ "title":"image update information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "image":{
+ "type":"object",
+ "properties":{
+ "path":{"oneOf": [path_schema, http_schema]},
+ "description":description_schema,
+ "name":name_schema,
+ "metadata":metadata_schema,
+ "public": yes_no_schema
+ },
+ "minProperties": 1,
+ "additionalProperties": False
+ }
+ },
+ "required": ["image"],
+ "additionalProperties": False
+}
+
+networks_schema={
+ "type":"array",
+ "items":{
+ "type":"object",
+ "properties":{
+ "name":name_schema,
+ "bandwidth":bandwidth_schema,
+ "vpci":pci_schema,
+ "uuid":id_schema,
+ "mac_address": mac_schema,
+ "model": {"type":"string", "enum":["virtio","e1000","ne2k_pci","pcnet","rtl8139"]},
+ "type": {"type":"string", "enum":["virtual","PF","VF","VFnotShared"]}
+ },
+ "additionalProperties": False,
+ "required": ["uuid"]
+ }
+}
+
+server_new_schema = {
+ "title":"server creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "server":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "name":name_schema,
+ "description":description_schema,
+ "start":{"type":"string", "enum":["yes","no","paused"]},
+ "hostId":id_schema,
+ "flavorRef":id_schema,
+ "imageRef":id_schema,
+ "extended": extended_schema,
+ "networks":networks_schema
+ },
+ "required": ["name","flavorRef","imageRef"]
+ }
+ },
+ "required": ["server"],
+ "additionalProperties": False
+}
+
+server_action_schema = {
+ "title":"server action information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "start":{"oneOf":[{"type": "null"}, {"type":"string", "enum":["rebuild","null"] }]},
+ "pause":{"type": "null"},
+ "resume":{"type": "null"},
+ "shutoff":{"type": "null"},
+ "shutdown":{"type": "null"},
+ "forceOff":{"type": "null"},
+ "terminate":{"type": "null"},
+ "createImage":{
+ "type":"object",
+ "properties":{
+ "path":path_schema,
+ "description":description_schema,
+ "name":name_schema,
+ "metadata":metadata_schema,
+ "imageRef": id_schema,
+ "disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
+ },
+ "required": ["name"]
+ },
+ "rebuild":{"type": ["object","null"]},
+ "reboot":{
+ "type": ["object","null"],
+# "properties": {
+# "type":{"type":"string", "enum":["SOFT"] }
+# },
+# "minProperties": 1,
+# "maxProperties": 1,
+# "additionalProperties": False
+ }
+ },
+ "minProperties": 1,
+ "maxProperties": 1,
+ "additionalProperties": False
+}
+
+network_new_schema = {
+ "title":"network creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "network":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "name":name_schema,
+ "type":{"type":"string", "enum":["bridge_man","bridge_data","data", "ptp"]},
+ "shared":{"type":"boolean"},
+ "tenant_id":id_schema,
+ "admin_state_up":{"type":"boolean"},
+ "provider:vlan":vlan_schema,
+ "provider:physical":net_bind_schema,
+ "cidr":cidr_schema,
+ "enable_dhcp": {"type":"boolean"},
+ "dhcp_first_ip": ip_schema,
+ "dhcp_last_ip": ip_schema,
+ "bind_net":name_schema, #can be name, or uuid
+ "bind_type":{"oneOf":[{"type":"null"},{"type":"string", "pattern":"^vlan:[0-9]{1,4}$"}]}
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["network"],
+ "additionalProperties": False
+}
+network_update_schema = {
+ "title":"network update information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "network":{
+ "type":"object",
+ "properties":{
+ "name":name_schema,
+ "type":{"type":"string", "enum":["bridge_man","bridge_data","data", "ptp"]},
+ "shared":{"type":"boolean"},
+ "tenant_id":id_schema,
+ "admin_state_up":{"type":"boolean"},
+ "provider:vlan":vlan_schema,
+ "provider:physical":net_bind_schema,
+ "cidr":cidr_schema,
+ "enable_dhcp": {"type":"boolean"},
+ "dhcp_first_ip": ip_schema,
+ "dhcp_last_ip": ip_schema,
+ "bind_net":name_schema, #can be name, or uuid
+ "bind_type":{"oneOf":[{"type":"null"},{"type":"string", "pattern":"^vlan:[0-9]{1,4}$"}]}
+ },
+ "minProperties": 1,
+ "additionalProperties": False
+ }
+ },
+ "required": ["network"],
+ "additionalProperties": False
+}
+
+
+port_new_schema = {
+ "title":"port creation information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "port":{
+ "type":"object",
+ "properties":{
+ "id":id_schema,
+ "name":nameshort_schema,
+ "network_id":{"oneOf":[{"type": "null"}, id_schema ]},
+ "tenant_id":id_schema,
+ "mac_address": {"oneOf":[{"type": "null"}, mac_schema] },
+ "admin_state_up":{"type":"boolean"},
+ "bandwidth":bandwidth_schema,
+ "binding:switch_port":nameshort_schema,
+ "binding:vlan": {"oneOf":[{"type": "null"}, vlan_schema ]}
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["port"],
+ "additionalProperties": False
+}
+
+port_update_schema = {
+ "title":"port update information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "port":{
+ "type":"object",
+ "properties":{
+ "name":nameshort_schema,
+ "network_id":{"anyOf":[{"type":"null"}, id_schema ] }
+ },
+ "minProperties": 1,
+ "additionalProperties": False
+ }
+ },
+ "required": ["port"],
+ "additionalProperties": False
+}
+
+localinfo_schema = {
+ "title":"localinfo information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "files":{ "type": "object"},
+ "inc_files":{ "type": "object"},
+ "server_files":{ "type": "object"}
+ },
+ "required": ["files"]
+}
+
+hostinfo_schema = {
+ "title":"host information schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type":"object",
+ "properties":{
+ "iface_names":{
+ "type":"object",
+ "patternProperties":{
+ ".":{ "type": "string"}
+ },
+ "minProperties": 1
+ }
+ },
+ "required": ["iface_names"]
+}