![Claudiu Belu](/assets/img/avatar_default.png)
Blueprint: hyper-v-wmi-v2 The Hyper-V APIs are mainly based on WMI. The original 2008 Hyper-V release introduced the "root\virtualization" namespace which got superseded in Hyper-V Server / Windows Server 2012 by the "root\virtualization\v2" namespace (referred as V2 in the sources). The original namespace has been dropped in the upcoming Hyper-V 2012 R2 (currently available in preview), which means that the Grizzly code will not be compatible with it as is. The Hyper-V driver is structured with a clear decoupling between OS interaction classes (so called *utils modules and classes) and the agent's logic. This allows us to provide an implementation of the V2 API without impacting the rest of the agent's code, based on a factory module added to instantiate the proper version of the *utils classes: the original "V1" ones for versions of the OS predating 2012 and the newer "V2" ones starting from Hyper-V 2012 (Windows kernel version 6.2). Change-Id: I380d8c65715242d8a5b94b5061ebe4f30e6c2090
162 lines
6.7 KiB
Python
162 lines
6.7 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'
|
|
_LAN_ENDPOINT = 'Msvm_LANEndpoint'
|
|
_STATE_DISABLED = 3
|
|
_OPERATION_MODE_ACCESS = 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]
|