21140e341a
Blueprint: hyper-v-metrics Hyper-V Server 2012 supports a new set of metrics API that can be used to provide switch port metrics data to external applications, e.g. Ceilometer. Metrics collection is disabled by default and can be enabled with a config option. Metrics are configured via ACLs applied by the Hyper-V plugin agent. Change-Id: Ife2a53db84936bae7c73b8c027022bbc5e89d48a
193 lines
8.0 KiB
Python
193 lines
8.0 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 Cloudbase Solutions SRL
|
|
# 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.
|
|
# @author: Alessandro Pilotti, Cloudbase Solutions Srl
|
|
# @author: Claudiu Belu, Cloudbase Solutions Srl
|
|
|
|
from neutron.plugins.hyperv.agent import utils
|
|
|
|
|
|
class HyperVUtilsV2(utils.HyperVUtils):
|
|
|
|
_EXTERNAL_PORT = 'Msvm_ExternalEthernetPort'
|
|
_ETHERNET_SWITCH_PORT = 'Msvm_EthernetSwitchPort'
|
|
_PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData'
|
|
_PORT_VLAN_SET_DATA = 'Msvm_EthernetSwitchPortVlanSettingData'
|
|
_PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData'
|
|
_LAN_ENDPOINT = 'Msvm_LANEndpoint'
|
|
_STATE_DISABLED = 3
|
|
_OPERATION_MODE_ACCESS = 1
|
|
|
|
_ACL_DIR_IN = 1
|
|
_ACL_DIR_OUT = 2
|
|
_ACL_TYPE_IPV4 = 2
|
|
_ACL_TYPE_IPV6 = 3
|
|
_ACL_ACTION_METER = 3
|
|
_ACL_APPLICABILITY_LOCAL = 1
|
|
|
|
_wmi_namespace = '//./root/virtualization/v2'
|
|
|
|
def __init__(self):
|
|
super(HyperVUtilsV2, self).__init__()
|
|
|
|
def connect_vnic_to_vswitch(self, vswitch_name, switch_port_name):
|
|
vnic = self._get_vnic_settings(switch_port_name)
|
|
vswitch = self._get_vswitch(vswitch_name)
|
|
|
|
port, found = self._get_switch_port_allocation(switch_port_name, True)
|
|
port.HostResource = [vswitch.path_()]
|
|
port.Parent = vnic.path_()
|
|
if not found:
|
|
vm = self._get_vm_from_res_setting_data(vnic)
|
|
self._add_virt_resource(vm, port)
|
|
else:
|
|
self._modify_virt_resource(port)
|
|
|
|
def _modify_virt_resource(self, res_setting_data):
|
|
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
(job_path, out_set_data, ret_val) = vs_man_svc.ModifyResourceSettings(
|
|
ResourceSettings=[res_setting_data.GetText_(1)])
|
|
self._check_job_status(ret_val, job_path)
|
|
|
|
def _add_virt_resource(self, vm, res_setting_data):
|
|
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
(job_path, out_set_data, ret_val) = vs_man_svc.AddResourceSettings(
|
|
vm.path_(), [res_setting_data.GetText_(1)])
|
|
self._check_job_status(ret_val, job_path)
|
|
|
|
def _remove_virt_resource(self, res_setting_data):
|
|
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
(job, ret_val) = vs_man_svc.RemoveResourceSettings(
|
|
ResourceSettings=[res_setting_data.path_()])
|
|
self._check_job_status(ret_val, job)
|
|
|
|
def disconnect_switch_port(
|
|
self, vswitch_name, switch_port_name, delete_port):
|
|
"""Disconnects the switch port."""
|
|
sw_port, found = self._get_switch_port_allocation(switch_port_name)
|
|
if not sw_port:
|
|
# Port not found. It happens when the VM was already deleted.
|
|
return
|
|
|
|
if delete_port:
|
|
self._remove_virt_resource(sw_port)
|
|
else:
|
|
sw_port.EnabledState = self._STATE_DISABLED
|
|
self._modify_virt_resource(sw_port)
|
|
|
|
def _get_vswitch(self, vswitch_name):
|
|
vswitch = self._conn.Msvm_VirtualEthernetSwitch(
|
|
ElementName=vswitch_name)
|
|
if not len(vswitch):
|
|
raise utils.HyperVException(msg=_('VSwitch not found: %s') %
|
|
vswitch_name)
|
|
return vswitch[0]
|
|
|
|
def _get_vswitch_external_port(self, vswitch):
|
|
vswitch_ports = vswitch.associators(
|
|
wmi_result_class=self._ETHERNET_SWITCH_PORT)
|
|
for vswitch_port in vswitch_ports:
|
|
lan_endpoints = vswitch_port.associators(
|
|
wmi_result_class=self._LAN_ENDPOINT)
|
|
if len(lan_endpoints):
|
|
lan_endpoints = lan_endpoints[0].associators(
|
|
wmi_result_class=self._LAN_ENDPOINT)
|
|
if len(lan_endpoints):
|
|
ext_port = lan_endpoints[0].associators(
|
|
wmi_result_class=self._EXTERNAL_PORT)
|
|
if ext_port:
|
|
return vswitch_port
|
|
|
|
def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name):
|
|
port_alloc, found = self._get_switch_port_allocation(switch_port_name)
|
|
if not found:
|
|
raise utils.HyperVException(
|
|
msg=_('Port Alloc not found: %s') % switch_port_name)
|
|
|
|
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
|
vlan_settings = self._get_vlan_setting_data_from_port_alloc(port_alloc)
|
|
if vlan_settings:
|
|
# Removing the feature because it cannot be modified
|
|
# due to a wmi exception.
|
|
(job_path, ret_val) = vs_man_svc.RemoveFeatureSettings(
|
|
FeatureSettings=[vlan_settings.path_()])
|
|
self._check_job_status(ret_val, job_path)
|
|
|
|
(vlan_settings, found) = self._get_vlan_setting_data(switch_port_name)
|
|
vlan_settings.AccessVlanId = vlan_id
|
|
vlan_settings.OperationMode = self._OPERATION_MODE_ACCESS
|
|
(job_path, out, ret_val) = vs_man_svc.AddFeatureSettings(
|
|
port_alloc.path_(), [vlan_settings.GetText_(1)])
|
|
self._check_job_status(ret_val, job_path)
|
|
|
|
def _get_vlan_setting_data_from_port_alloc(self, port_alloc):
|
|
return self._get_first_item(port_alloc.associators(
|
|
wmi_result_class=self._PORT_VLAN_SET_DATA))
|
|
|
|
def _get_vlan_setting_data(self, switch_port_name, create=True):
|
|
return self._get_setting_data(
|
|
self._PORT_VLAN_SET_DATA,
|
|
switch_port_name, create)
|
|
|
|
def _get_switch_port_allocation(self, switch_port_name, create=False):
|
|
return self._get_setting_data(
|
|
self._PORT_ALLOC_SET_DATA,
|
|
switch_port_name, create)
|
|
|
|
def _get_setting_data(self, class_name, element_name, create=True):
|
|
element_name = element_name.replace("'", '"')
|
|
q = self._conn.query("SELECT * FROM %(class_name)s WHERE "
|
|
"ElementName = '%(element_name)s'" %
|
|
{"class_name": class_name,
|
|
"element_name": element_name})
|
|
data = self._get_first_item(q)
|
|
found = data is not None
|
|
if not data and create:
|
|
data = self._get_default_setting_data(class_name)
|
|
data.ElementName = element_name
|
|
return data, found
|
|
|
|
def _get_default_setting_data(self, class_name):
|
|
return self._conn.query("SELECT * FROM %s WHERE InstanceID "
|
|
"LIKE '%%\\Default'" % class_name)[0]
|
|
|
|
def _get_first_item(self, obj):
|
|
if obj:
|
|
return obj[0]
|
|
|
|
def enable_port_metrics_collection(self, switch_port_name):
|
|
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
|
if not found:
|
|
return
|
|
|
|
# Add the ACLs only if they don't already exist
|
|
acls = port.associators(wmi_result_class=self._PORT_ALLOC_ACL_SET_DATA)
|
|
for acl_type in [self._ACL_TYPE_IPV4, self._ACL_TYPE_IPV6]:
|
|
for acl_dir in [self._ACL_DIR_IN, self._ACL_DIR_OUT]:
|
|
acls = [v for v in acls
|
|
if v.Action == self._ACL_ACTION_METER and
|
|
v.Applicability == self._ACL_APPLICABILITY_LOCAL and
|
|
v.Direction == acl_dir and
|
|
v.AclType == acl_type]
|
|
if not acls:
|
|
acl = self._get_default_setting_data(
|
|
self._PORT_ALLOC_ACL_SET_DATA)
|
|
acl.AclType = acl_type
|
|
acl.Direction = acl_dir
|
|
acl.Action = self._ACL_ACTION_METER
|
|
acl.Applicability = self._ACL_APPLICABILITY_LOCAL
|
|
self._add_virt_feature(port, acl)
|