summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/tests/test_powervm.py165
-rw-r--r--nova/virt/powervm/__init__.py29
-rw-r--r--nova/virt/powervm/command.py90
-rw-r--r--nova/virt/powervm/common.py112
-rw-r--r--nova/virt/powervm/constants.py35
-rw-r--r--nova/virt/powervm/driver.py214
-rw-r--r--nova/virt/powervm/exception.py59
-rw-r--r--nova/virt/powervm/lpar.py158
-rw-r--r--nova/virt/powervm/operator.py631
9 files changed, 1493 insertions, 0 deletions
diff --git a/nova/tests/test_powervm.py b/nova/tests/test_powervm.py
new file mode 100644
index 000000000..13d9cdd58
--- /dev/null
+++ b/nova/tests/test_powervm.py
@@ -0,0 +1,165 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+"""
+Test suite for PowerVMDriver.
+"""
+
+from nova.compute import power_state
+from nova import context
+from nova import db
+from nova import flags
+from nova import test
+
+from nova.openstack.common import log as logging
+from nova.virt import images
+from nova.virt.powervm import driver as powervm_driver
+from nova.virt.powervm import lpar
+from nova.virt.powervm import operator
+
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger(__name__)
+
+
+def fake_lpar(instance_name):
+ return lpar.LPAR(name=instance_name,
+ lpar_id=1, desired_mem=1024,
+ max_mem=2048, max_procs=2,
+ uptime=939395, state='Running')
+
+
+class FakeIVMOperator(object):
+
+ def get_lpar(self, instance_name, resource_type='lpar'):
+ return fake_lpar(instance_name)
+
+ def list_lpar_instances(self):
+ return ['instance-00000001', 'instance-00000002']
+
+ def create_lpar(self, lpar):
+ pass
+
+ def start_lpar(self, instance_name):
+ pass
+
+ def stop_lpar(self, instance_name):
+ pass
+
+ def remove_lpar(self, instance_name):
+ pass
+
+ def get_vhost_by_instance_id(self, instance_id):
+ return 'vhostfake'
+
+ def get_virtual_eth_adapter_id(self):
+ return 1
+
+ def get_disk_name_by_vhost(self, vhost):
+ return 'lvfake01'
+
+ def remove_disk(self, disk_name):
+ pass
+
+ def create_logical_volume(self, size):
+ return 'lvfake01'
+
+ def remove_logical_volume(self, lv_name):
+ pass
+
+ def copy_file_to_device(self, sourcePath, device):
+ pass
+
+ def copy_image_file(self, sourcePath, remotePath):
+ finalPath = '/home/images/rhel62.raw.7e358754160433febd6f3318b7c9e335'
+ size = 4294967296
+ return finalPath, size
+
+ def run_cfg_dev(self, device_name):
+ pass
+
+ def attach_disk_to_vhost(self, disk, vhost):
+ pass
+
+ def get_memory_info(self):
+ return {'total_mem': 65536, 'avail_mem': 46336}
+
+ def get_cpu_info(self):
+ return {'total_procs': 8.0, 'avail_procs': 6.3}
+
+ def get_disk_info(self):
+ return {'disk_total': 10168,
+ 'disk_used': 0,
+ 'disk_avail': 10168}
+
+
+def fake_get_powervm_operator():
+ return FakeIVMOperator()
+
+
+class PowerVMDriverTestCase(test.TestCase):
+ """Unit tests for PowerVM connection calls."""
+
+ def setUp(self):
+ super(PowerVMDriverTestCase, self).setUp()
+ self.stubs.Set(operator, 'get_powervm_operator',
+ fake_get_powervm_operator)
+ self.powervm_connection = powervm_driver.PowerVMDriver()
+ self.instance = self._create_instance()
+
+ def _create_instance(self):
+ return db.instance_create(context.get_admin_context(),
+ {'user_id': 'fake',
+ 'project_id': 'fake',
+ 'instance_type_id': 1,
+ 'memory_mb': 1024,
+ 'vcpus': 2})
+
+ def test_list_instances(self):
+ instances = self.powervm_connection.list_instances()
+ self.assertTrue('instance-00000001' in instances)
+ self.assertTrue('instance-00000002' in instances)
+
+ def test_instance_exists(self):
+ name = self.instance['name']
+ self.assertTrue(self.powervm_connection.instance_exists(name))
+
+ def test_spawn(self):
+ def fake_image_fetch_to_raw(context, image_id, file_path,
+ user_id, project_id):
+ pass
+ self.flags(powervm_img_local_path='/images/')
+ self.stubs.Set(images, 'fetch_to_raw', fake_image_fetch_to_raw)
+ image_meta = {}
+ image_meta['id'] = '666'
+ self.powervm_connection.spawn(context.get_admin_context(),
+ self.instance, image_meta)
+ state = self.powervm_connection.get_info(self.instance)['state']
+ self.assertEqual(state, power_state.RUNNING)
+
+ def test_destroy(self):
+ self.powervm_connection.destroy(self.instance, None)
+ self.stubs.Set(FakeIVMOperator, 'get_lpar', lambda x, y: None)
+ name = self.instance['name']
+ self.assertFalse(self.powervm_connection.instance_exists(name))
+
+ def test_get_info(self):
+ info = self.powervm_connection.get_info(self.instance)
+ self.assertEqual(info['state'], power_state.RUNNING)
+ self.assertEqual(info['max_mem'], 2048)
+ self.assertEqual(info['mem'], 1024)
+ self.assertEqual(info['num_cpu'], 2)
+ self.assertEqual(info['cpu_time'], 939395)
diff --git a/nova/virt/powervm/__init__.py b/nova/virt/powervm/__init__.py
new file mode 100644
index 000000000..83bbcd289
--- /dev/null
+++ b/nova/virt/powervm/__init__.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+"""A connection to an IBM PowerVM Virtualization system.
+
+The driver connects to an Integrated Virtualization Manager (IVM) to
+perform PowerVM Logical Partition (LPAR) deployment and management.
+
+For more detailed information about PowerVM virtualization,
+refer to the IBM Redbook[1] publication.
+
+[1] IBM Redbook. IBM PowerVM Virtualization Introduction and Configuration.
+ May 2011. <http://www.redbooks.ibm.com/abstracts/sg247940.html>
+"""
+
+from nova.virt.powervm.driver import PowerVMDriver
diff --git a/nova/virt/powervm/command.py b/nova/virt/powervm/command.py
new file mode 100644
index 000000000..8cef5b728
--- /dev/null
+++ b/nova/virt/powervm/command.py
@@ -0,0 +1,90 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+
+"""PowerVM manager commands."""
+
+
+class BaseCommand(object):
+
+ def lsvg(self, args=''):
+ return 'lsvg %s' % args
+
+ def mklv(self, args=''):
+ return 'mklv %s' % args
+
+ def rmdev(self, args=''):
+ return 'rmdev %s' % args
+
+ def rmvdev(self, args=''):
+ return 'rmvdev %s' % args
+
+ def lsmap(self, args=''):
+ return 'lsmap %s' % args
+
+ def lsdev(self, args=''):
+ return 'lsdev %s' % args
+
+ def rmsyscfg(self, args=''):
+ return 'rmsyscfg %s' % args
+
+ def chsysstate(self, args=''):
+ return 'chsysstate %s' % args
+
+ def mksyscfg(self, args=''):
+ return 'mksyscfg %s' % args
+
+ def lssyscfg(self, args=''):
+ return 'lssyscfg %s' % args
+
+ def cfgdev(self, args=''):
+ return 'cfgdev %s' % args
+
+ def mkvdev(self, args=''):
+ return 'mkvdev %s' % args
+
+ def lshwres(self, args=''):
+ return 'lshwres %s' % args
+
+ def vhost_by_instance_id(self, instance_id_hex):
+ pass
+
+
+class IVMCommand(BaseCommand):
+
+ def lsvg(self, args=''):
+ return 'ioscli ' + BaseCommand.lsvg(self, args)
+
+ def mklv(self, args=''):
+ return 'ioscli ' + BaseCommand.mklv(self, args)
+
+ def rmdev(self, args=''):
+ return 'ioscli ' + BaseCommand.rmdev(self, args)
+
+ def rmvdev(self, args=''):
+ return 'ioscli ' + BaseCommand.rmvdev(self, args=args)
+
+ def lsmap(self, args=''):
+ return 'ioscli ' + BaseCommand.lsmap(self, args)
+
+ def lsdev(self, args=''):
+ return 'ioscli ' + BaseCommand.lsdev(self, args)
+
+ def cfgdev(self, args=''):
+ return 'ioscli ' + BaseCommand.cfgdev(self, args=args)
+
+ def mkvdev(self, args=''):
+ return 'ioscli ' + BaseCommand.mkvdev(self, args=args)
diff --git a/nova/virt/powervm/common.py b/nova/virt/powervm/common.py
new file mode 100644
index 000000000..179bd7f14
--- /dev/null
+++ b/nova/virt/powervm/common.py
@@ -0,0 +1,112 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+import ftplib
+import os
+
+import paramiko
+
+from nova import exception as nova_exception
+from nova.openstack.common import log as logging
+from nova.virt.powervm import exception
+
+LOG = logging.getLogger(__name__)
+
+
+class Connection(object):
+
+ def __init__(self, host, username, password, port=22):
+ self.host = host
+ self.username = username
+ self.password = password
+ self.port = port
+
+
+def ssh_connect(connection):
+ """Method to connect to remote system using ssh protocol.
+
+ :param connection: a Connection object.
+ :returns: paramiko.SSHClient -- an active ssh connection.
+ :raises: PowerVMConnectionFailed
+ """
+ try:
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ ssh.connect(connection.host,
+ username=connection.username,
+ password=connection.password,
+ port=connection.port)
+ return ssh
+ except Exception:
+ LOG.exception(_('Connection error connecting PowerVM manager'))
+ raise exception.PowerVMConnectionFailed()
+
+
+def ssh_command_as_root(ssh_connection, cmd, check_exit_code=True):
+ """Method to execute remote command as root.
+
+ :param connection: an active paramiko.SSHClient connection.
+ :param command: string containing the command to run.
+ :returns: Tuple -- a tuple of (stdout, stderr)
+ :raises: nova.exception.ProcessExecutionError
+ """
+ chan = ssh_connection._transport.open_session()
+ # This command is required to be executed
+ # in order to become root.
+ chan.exec_command('ioscli oem_setup_env')
+ bufsize = -1
+ stdin = chan.makefile('wb', bufsize)
+ stdout = chan.makefile('rb', bufsize)
+ stderr = chan.makefile_stderr('rb', bufsize)
+ # We run the command and then call 'exit' to exit from
+ # super user environment.
+ stdin.write('%s\n%s\n' % (cmd, 'exit'))
+ stdin.flush()
+ exit_status = chan.recv_exit_status()
+
+ # Lets handle the error just like nova.utils.ssh_execute does.
+ if exit_status != -1:
+ LOG.debug(_('Result was %s') % exit_status)
+ if check_exit_code and exit_status != 0:
+ raise nova_exception.ProcessExecutionError(exit_code=exit_status,
+ stdout=stdout,
+ stderr=stderr,
+ cmd=' '.join(cmd))
+
+ return (stdout, stderr)
+
+
+def ftp_put_command(connection, local_path, remote_dir):
+ """Method to transfer a file via ftp.
+
+ :param connection: a Connection object.
+ :param local_path: path to the local file
+ :param remote_dir: path to remote destination
+ :raises: PowerVMFileTransferFailed
+ """
+ try:
+ ftp = ftplib.FTP(host=connection.host,
+ user=connection.username,
+ passwd=connection.password)
+ ftp.cwd(remote_dir)
+ name = os.path.split(local_path)[1]
+ f = open(local_path, "rb")
+ ftp.storbinary("STOR " + name, f)
+ f.close()
+ ftp.close()
+ except Exception:
+ LOG.exception(_('File transfer to PowerVM manager failed'))
+ raise exception.PowerVMFileTransferFailed(file_path=local_path)
diff --git a/nova/virt/powervm/constants.py b/nova/virt/powervm/constants.py
new file mode 100644
index 000000000..1990ec5a5
--- /dev/null
+++ b/nova/virt/powervm/constants.py
@@ -0,0 +1,35 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+from nova.compute import power_state
+
+POWERVM_NOSTATE = ''
+POWERVM_RUNNING = 'Running'
+POWERVM_SHUTDOWN = 'Not Activated'
+POWERVM_POWER_STATE = {
+ POWERVM_NOSTATE: power_state.NOSTATE,
+ POWERVM_RUNNING: power_state.RUNNING,
+ POWERVM_SHUTDOWN: power_state.SHUTDOWN,
+}
+
+POWERVM_CPU_INFO = ('ppc64', 'powervm', '3940')
+POWERVM_HYPERVISOR_TYPE = 'powervm'
+POWERVM_HYPERVISOR_VERSION = '7.1'
+
+POWERVM_MIN_MEM = 512
+POWERVM_MAX_MEM = 1024
+POWERVM_MAX_CPUS = 1
+POWERVM_MIN_CPUS = 1
diff --git a/nova/virt/powervm/driver.py b/nova/virt/powervm/driver.py
new file mode 100644
index 000000000..66fd8929e
--- /dev/null
+++ b/nova/virt/powervm/driver.py
@@ -0,0 +1,214 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+from nova.compute import task_states
+from nova.compute import vm_states
+
+from nova import context as nova_context
+from nova import db
+from nova import flags
+
+from nova.openstack.common import cfg
+from nova.openstack.common import log as logging
+
+from nova.virt import driver
+from nova.virt.powervm import operator
+
+
+LOG = logging.getLogger(__name__)
+
+powervm_opts = [
+ cfg.StrOpt('powervm_mgr_type',
+ default='ivm',
+ help='PowerVM manager type (ivm, hmc)'),
+ cfg.StrOpt('powervm_mgr',
+ default=None,
+ help='PowerVM manager host or ip'),
+ cfg.StrOpt('powervm_vios',
+ default='powervm_mgr',
+ help='PowerVM vios host or ip if different from manager'),
+ cfg.StrOpt('powervm_mgr_user',
+ default=None,
+ help='PowerVM manager user name'),
+ cfg.StrOpt('powervm_mgr_passwd',
+ default=None,
+ help='PowerVM manager user password'),
+ cfg.StrOpt('powervm_img_remote_path',
+ default=None,
+ help='PowerVM image remote path'),
+ cfg.StrOpt('powervm_img_local_path',
+ default=None,
+ help='Local directory to download glance images to'),
+ ]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(powervm_opts)
+
+
+class PowerVMDriver(driver.ComputeDriver):
+
+ """PowerVM Implementation of Compute Driver."""
+
+ def __init__(self):
+ super(PowerVMDriver, self).__init__()
+ self._powervm = operator.PowerVMOperator()
+
+ @property
+ def host_state(self):
+ pass
+
+ def init_host(self, host):
+ """Initialize anything that is necessary for the driver to function,
+ including catching up with currently running VM's on the given host."""
+ context = nova_context.get_admin_context()
+ instances = db.instance_get_all_by_host(context, host)
+ powervm_instances = self.list_instances()
+ # Looks for db instances that don't exist on the host side
+ # and cleanup the inconsistencies.
+ for db_instance in instances:
+ task_state = db_instance['task_state']
+ if db_instance['name'] in powervm_instances:
+ continue
+ if task_state in [task_states.DELETING, task_states.SPAWNING]:
+ db.instance_update(context, db_instance['uuid'],
+ {'vm_state': vm_states.DELETED,
+ 'task_state': None})
+ db.instance_destroy(context, db_instance['uuid'])
+
+ def get_info(self, instance):
+ """Get the current status of an instance."""
+ return self._powervm.get_info(instance['name'])
+
+ def get_num_instances(self):
+ return len(self.list_instances())
+
+ def instance_exists(self, instance_name):
+ return self._powervm.instance_exists(instance_name)
+
+ def list_instances(self):
+ return self._powervm.list_instances()
+
+ def list_instances_detail(self):
+ """Return a list of InstanceInfo for all registered VMs"""
+ infos = []
+ for instance_name in self.list_instances():
+ state = self._powervm.get_info(instance_name)['state']
+ infos.append(driver.InstanceInfo(instance_name, state))
+ return infos
+
+ def get_host_stats(self, refresh=False):
+ """Return currently known host stats"""
+ return self._powervm.get_host_stats(refresh=refresh)
+
+ def plug_vifs(self, instance, network_info):
+ pass
+
+ def spawn(self, context, instance, image_meta,
+ network_info=None, block_device_info=None):
+ """
+ Create a new instance/VM/domain on powerVM.
+
+ :param context: security context
+ :param instance: Instance object as returned by DB layer.
+ This function should use the data there to guide
+ the creation of the new instance.
+ :param image_meta: image object returned by nova.image.glance that
+ defines the image from which to boot this instance
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ :param block_device_info: Information about block devices to be
+ attached to the instance.
+ """
+ self._powervm.spawn(context, instance, image_meta['id'])
+
+ def destroy(self, instance, network_info, block_device_info=None):
+ """Destroy (shutdown and delete) the specified instance."""
+ self._powervm.destroy(instance['name'])
+
+ def reboot(self, instance, network_info, reboot_type):
+ """Reboot the specified instance.
+
+ :param instance: Instance object as returned by DB layer.
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ :param reboot_type: Either a HARD or SOFT reboot
+ """
+ # TODO(Vek): Need to pass context in for access to auth_token
+ pass
+
+ def get_host_ip_addr(self):
+ """
+ Retrieves the IP address of the dom0
+ """
+ pass
+
+ def pause(self, instance):
+ """Pause the specified instance."""
+ pass
+
+ def unpause(self, instance):
+ """Unpause paused VM instance"""
+ pass
+
+ def suspend(self, instance):
+ """suspend the specified instance"""
+ pass
+
+ def resume(self, instance):
+ """resume the specified instance"""
+ pass
+
+ def power_off(self, instance):
+ """Power off the specified instance."""
+ self._powervm.power_off(instance['name'])
+
+ def power_on(self, instance):
+ """Power on the specified instance"""
+ self._powervm.power_on(instance['name'])
+
+ def update_available_resource(self, ctxt, host):
+ """Updates compute manager resource info on ComputeNode table.
+
+ This method is called when nova-compute launches, and
+ whenever admin executes "nova-manage service update_resource".
+
+ :param ctxt: security context
+ :param host: hostname that compute manager is currently running
+
+ """
+ pass
+
+ def host_power_action(self, host, action):
+ """Reboots, shuts down or powers up the host."""
+ pass
+
+ def legacy_nwinfo(self):
+ """
+ Indicate if the driver requires the legacy network_info format.
+ """
+ # TODO(tr3buchet): update all subclasses and remove this
+ return False
+
+ def manage_image_cache(self, context):
+ """
+ Manage the driver's local image cache.
+
+ Some drivers chose to cache images for instances on disk. This method
+ is an opportunity to do management of that cache which isn't directly
+ related to other calls into the driver. The prime example is to clean
+ the cache and remove images which are no longer of interest.
+ """
+ pass
diff --git a/nova/virt/powervm/exception.py b/nova/virt/powervm/exception.py
new file mode 100644
index 000000000..1ced07cfe
--- /dev/null
+++ b/nova/virt/powervm/exception.py
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+from nova import exception
+
+
+class PowerVMConnectionFailed(exception.NovaException):
+ message = _('Connection to PowerVM manager failed')
+
+
+class PowerVMFileTransferFailed(exception.NovaException):
+ message = _("File '%(file_path)' transfer to PowerVM manager failed")
+
+
+class PowerVMLPARInstanceNotFound(exception.NovaException):
+ message = _("LPAR instance '%(instance_name)s' could not be found")
+
+
+class PowerVMLPARCreationFailed(exception.NovaException):
+ message = _("LPAR instance '%(instance_name)s' creation failed")
+
+
+class PowerVMNoSpaceLeftOnVolumeGroup(exception.NovaException):
+ message = _("No space left on any volume group")
+
+
+class PowerVMLPARAttributeNotFound(exception.NovaException):
+ pass
+
+
+class PowerVMImageCreationFailed(exception.NovaException):
+ message = _("Image creation failed on PowerVM")
+
+
+class PowerVMInsufficientFreeMemory(exception.NovaException):
+ message = _("Insufficient free memory on PowerVM system to spawn instance "
+ "'%(instance_name)s'")
+
+
+class PowerVMInsufficientCPU(exception.NovaException):
+ message = _("Insufficient available CPUs on PowerVM system to spawn "
+ "instance '%(instance_name)s'")
+
+
+class PowerVMLPARInstanceCleanupFailed(exception.NovaException):
+ message = _("PowerVM LPAR instance '%(instance_name)s' cleanup failed")
diff --git a/nova/virt/powervm/lpar.py b/nova/virt/powervm/lpar.py
new file mode 100644
index 000000000..10e8c8e37
--- /dev/null
+++ b/nova/virt/powervm/lpar.py
@@ -0,0 +1,158 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+"""PowerVM Logical Partition (LPAR)
+
+PowerVM LPAR configuration attributes.
+"""
+
+import shlex
+
+from nova.openstack.common import log as logging
+from nova.virt.powervm import exception
+
+LOG = logging.getLogger(__name__)
+
+
+def load_from_conf_data(conf_data):
+ """LPAR configuration data parser.
+
+ The configuration data is a string representation of
+ the attributes of a Logical Partition. The attributes
+ consists of name/value pairs, which are in command separated
+ value format.
+ Example format: name=lpar_name,lpar_id=1,lpar_env=aixlinux
+
+ :param conf_data: string containing the LPAR configuration data.
+ :returns: LPAR -- LPAR object.
+ """
+ # config_data can contain comma separated values within
+ # double quotes, example: virtual_serial_adapters
+ # and virtual_scsi_adapters attributes. So can't simply
+ # split them by ','.
+ cf_splitter = shlex.shlex(conf_data, posix=True)
+ cf_splitter.whitespace = ','
+ cf_splitter.whitespace_split = True
+ attribs = dict(item.split("=") for item in list(cf_splitter))
+ lpar = LPAR()
+ for (key, value) in attribs.items():
+ lpar[key] = value
+ return lpar
+
+
+class LPAR(object):
+
+ """
+ Simple class representing a logical partition and the attributes
+ for the partition and/or its selected profile.
+ """
+
+ # Attributes for all logical partitions
+ LPAR_ATTRS = (
+ 'name',
+ 'lpar_id',
+ 'lpar_env',
+ 'state',
+ 'resource_config',
+ 'os_version',
+ 'logical_serial_num',
+ 'default_profile',
+ 'profile_name',
+ 'curr_profile',
+ 'work_group_id',
+ 'allow_perf_collection',
+ 'power_ctrl_lpar_ids',
+ 'boot_mode',
+ 'lpar_keylock',
+ 'auto_start',
+ 'uptime',
+ 'lpar_avail_priority',
+ 'desired_lpar_proc_compat_mode',
+ 'curr_lpar_proc_compat_mode',
+ 'virtual_eth_mac_base_value',
+ 'rmc_ipaddr'
+ )
+
+ # Logical partitions may contain one or more profiles, which
+ # may have the following attributes
+ LPAR_PROFILE_ATTRS = (
+ 'name',
+ 'lpar_name',
+ 'lpar_id',
+ 'os_type',
+ 'all_resources',
+ 'mem_mode',
+ 'min_mem',
+ 'desired_mem',
+ 'max_mem',
+ 'proc_mode',
+ 'min_proc_units',
+ 'desired_proc_units',
+ 'max_proc_units',
+ 'min_procs',
+ 'desired_procs',
+ 'max_procs',
+ 'sharing_mode',
+ 'uncap_weight',
+ 'io_slots',
+ 'lpar_io_pool_ids',
+ 'max_virtual_slots',
+ 'virtual_serial_adapters',
+ 'virtual_scsi_adapters',
+ 'virtual_eth_adapters',
+ 'boot_mode',
+ 'conn_monitoring',
+ 'auto_start',
+ 'power_ctrl_lpar_ids',
+ 'lhea_logical_ports',
+ 'lhea_capabilities',
+ 'lpar_proc_compat_mode',
+ 'virtual_fc_adapters'
+ )
+
+ def __init__(self, **kwargs):
+ self.attributes = dict([k, None] for k in self.LPAR_ATTRS)
+ self.profile_attributes = dict([k, None] for k
+ in self.LPAR_PROFILE_ATTRS)
+ self.attributes.update(kwargs)
+ self.profile_attributes.update(kwargs)
+ self.all_attrs = dict(self.attributes.items()
+ + self.profile_attributes.items())
+
+ def __getitem__(self, key):
+ if key not in self.all_attrs.keys():
+ raise exception.PowerVMLPARAttributeNotFound(key)
+ return self.all_attrs.get(key)
+
+ def __setitem__(self, key, value):
+ if key not in self.all_attrs.keys():
+ raise exception.PowerVMLPARAttributeNotFound(key)
+ self.all_attrs[key] = value
+
+ def __delitem__(self, key):
+ if key not in self.all_attrs.keys():
+ raise exception.PowerVMLPARAttributeNotFound(key)
+ # We set to None instead of removing the key...
+ self.all_attrs[key] = None
+
+ def to_string(self, exclude_attribs=[]):
+ conf_data = []
+ for (key, value) in self.all_attrs.items():
+ if key in exclude_attribs or value is None:
+ continue
+ conf_data.append('%s=%s' % (key, value))
+
+ return ','.join(conf_data)
diff --git a/nova/virt/powervm/operator.py b/nova/virt/powervm/operator.py
new file mode 100644
index 000000000..110fae4fb
--- /dev/null
+++ b/nova/virt/powervm/operator.py
@@ -0,0 +1,631 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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.
+
+import decimal
+import hashlib
+import os
+import re
+import time
+
+from nova import exception as nova_exception
+from nova import flags
+from nova import utils
+
+from nova.compute import power_state
+from nova.openstack.common import log as logging
+from nova.virt import images
+from nova.virt.powervm import command
+from nova.virt.powervm import common
+from nova.virt.powervm import constants
+from nova.virt.powervm import exception
+from nova.virt.powervm import lpar as LPAR
+
+
+LOG = logging.getLogger(__name__)
+FLAGS = flags.FLAGS
+
+
+def get_powervm_operator():
+ if FLAGS.powervm_mgr_type == 'ivm':
+ return IVMOperator(common.Connection(FLAGS.powervm_mgr,
+ FLAGS.powervm_mgr_user,
+ FLAGS.powervm_mgr_passwd))
+
+
+class PowerVMOperator(object):
+ """PowerVM main operator.
+
+ The PowerVMOperator is intented to wrapper all operations
+ from the driver and handle either IVM or HMC managed systems.
+ """
+
+ def __init__(self):
+ self._operator = get_powervm_operator()
+ self._host_stats = {}
+ self._update_host_stats()
+
+ def get_info(self, instance_name):
+ """Get the current status of an LPAR instance.
+
+ Returns a dict containing:
+
+ :state: the running state, one of the power_state codes
+ :max_mem: (int) the maximum memory in KBytes allowed
+ :mem: (int) the memory in KBytes used by the domain
+ :num_cpu: (int) the number of virtual CPUs for the domain
+ :cpu_time: (int) the CPU time used in nanoseconds
+
+ :raises: PowerVMLPARInstanceNotFound
+ """
+ lpar_instance = self._get_instance(instance_name)
+
+ state = constants.POWERVM_POWER_STATE[lpar_instance['state']]
+ return {'state': state,
+ 'max_mem': lpar_instance['max_mem'],
+ 'mem': lpar_instance['desired_mem'],
+ 'num_cpu': lpar_instance['max_procs'],
+ 'cpu_time': lpar_instance['uptime']}
+
+ def instance_exists(self, instance_name):
+ lpar_instance = self._operator.get_lpar(instance_name)
+ return True if lpar_instance else False
+
+ def _get_instance(self, instance_name):
+ """Check whether or not the LPAR instance exists and return it."""
+ lpar_instance = self._operator.get_lpar(instance_name)
+
+ if lpar_instance is None:
+ LOG.exception(_("LPAR instance '%s' not found") % instance_name)
+ raise exception.PowerVMLPARInstanceNotFound(
+ instance_name=instance_name)
+ return lpar_instance
+
+ def list_instances(self):
+ """
+ Return the names of all the instances known to the virtualization
+ layer, as a list.
+ """
+ lpar_instances = self._operator.list_lpar_instances()
+ # We filter out instances that haven't been created
+ # via Openstack. Notice that this is fragile and it can
+ # be improved later.
+ instances = [instance for instance in lpar_instances
+ if re.search(r'^instance-[0-9]{8}$', instance)]
+ return instances
+
+ def get_host_stats(self, refresh=False):
+ """Return currently known host stats"""
+ if refresh:
+ self._update_host_stats()
+ return self._host_stats
+
+ def _update_host_stats(self):
+ memory_info = self._operator.get_memory_info()
+ cpu_info = self._operator.get_cpu_info()
+
+ # Note: disk avail information is not accurate. The value
+ # is a sum of all Volume Groups and the result cannot
+ # represent the real possibility. Example: consider two
+ # VGs both 10G, the avail disk will be 20G however,
+ # a 15G image does not fit in any VG. This can be improved
+ # later on.
+ disk_info = self._operator.get_disk_info()
+
+ data = {}
+ data['vcpus'] = cpu_info['total_procs']
+ data['vcpus_used'] = cpu_info['total_procs'] - cpu_info['avail_procs']
+ data['cpu_info'] = constants.POWERVM_CPU_INFO
+ data['disk_total'] = disk_info['disk_total']
+ data['disk_used'] = disk_info['disk_used']
+ data['disk_available'] = disk_info['disk_avail']
+ data['host_memory_total'] = memory_info['total_mem']
+ data['host_memory_free'] = memory_info['avail_mem']
+ data['hypervisor_type'] = constants.POWERVM_HYPERVISOR_TYPE
+ data['hypervisor_version'] = constants.POWERVM_HYPERVISOR_VERSION
+ data['extres'] = ''
+
+ self._host_stats = data
+
+ def spawn(self, context, instance, image_id):
+ def _create_lpar_instance(instance):
+ host_stats = self.get_host_stats(refresh=True)
+ inst_name = instance['name']
+
+ # CPU/Memory min and max can be configurable. Lets assume
+ # some default values for now.
+
+ # Memory
+ mem = instance['memory_mb']
+ if mem > host_stats['host_memory_free']:
+ LOG.exception(_('Not enough free memory in the host'))
+ raise exception.PowerVMInsufficientFreeMemory(
+ instance_name=instance['name'])
+ mem_min = min(mem, constants.POWERVM_MIN_MEM)
+ mem_max = mem + constants.POWERVM_MAX_MEM
+
+ # CPU
+ cpus = instance['vcpus']
+ avail_cpus = host_stats['vcpus'] - host_stats['vcpus_used']
+ if cpus > avail_cpus:
+ LOG.exception(_('Insufficient available CPU on PowerVM'))
+ raise exception.PowerVMInsufficientCPU(
+ instance_name=instance['name'])
+ cpus_min = min(cpus, constants.POWERVM_MIN_CPUS)
+ cpus_max = cpus + constants.POWERVM_MAX_CPUS
+ cpus_units_min = decimal.Decimal(cpus_min) / decimal.Decimal(10)
+ cpus_units = decimal.Decimal(cpus) / decimal.Decimal(10)
+
+ try:
+ # Network
+ eth_id = self._operator.get_virtual_eth_adapter_id()
+
+ # LPAR configuration data
+ lpar_inst = LPAR.LPAR(
+ name=inst_name, lpar_env='aixlinux',
+ min_mem=mem_min, desired_mem=mem,
+ max_mem=mem_max, proc_mode='shared',
+ sharing_mode='uncap', min_procs=cpus_min,
+ desired_procs=cpus, max_procs=cpus_max,
+ min_proc_units=cpus_units_min,
+ desired_proc_units=cpus_units,
+ max_proc_units=cpus_max,
+ virtual_eth_adapters='4/0/%s//0/0' % eth_id)
+
+ LOG.debug(_("Creating LPAR instance '%s'") % instance['name'])
+ self._operator.create_lpar(lpar_inst)
+ except nova_exception.ProcessExecutionError:
+ LOG.exception(_("LPAR instance '%s' creation failed") %
+ instance['name'])
+ raise exception.PowerVMLPARCreationFailed()
+
+ def _create_image(context, instance, image_id):
+ """Fetch image from glance and copy it to the remote system."""
+ try:
+ file_name = '.'.join([image_id, 'gz'])
+ file_path = os.path.join(FLAGS.powervm_img_local_path,
+ file_name)
+ LOG.debug(_("Fetching image '%s' from glance") % image_id)
+ images.fetch_to_raw(context, image_id, file_path,
+ instance['user_id'],
+ project_id=instance['project_id'])
+ LOG.debug(_("Copying image '%s' to IVM") % file_path)
+ remote_path = FLAGS.powervm_img_remote_path
+ remote_file_name, size = self._operator.copy_image_file(
+ file_path, remote_path)
+ # Logical volume
+ LOG.debug(_("Creating logical volume"))
+ lpar_id = self._operator.get_lpar(instance['name'])['lpar_id']
+ vhost = self._operator.get_vhost_by_instance_id(lpar_id)
+ disk_name = self._operator.create_logical_volume(size)
+ self._operator.attach_disk_to_vhost(disk_name, vhost)
+ LOG.debug(_("Copying image to the device '%s'") % disk_name)
+ self._operator.copy_file_to_device(remote_file_name, disk_name)
+ except Exception, e:
+ LOG.exception(_("PowerVM image creation failed: %s") % str(e))
+ raise exception.PowerVMImageCreationFailed()
+
+ try:
+ _create_lpar_instance(instance)
+ _create_image(context, instance, image_id)
+ LOG.debug(_("Activating the LPAR instance '%s'")
+ % instance['name'])
+ self._operator.start_lpar(instance['name'])
+
+ # Wait for boot
+ timeout_count = range(10)
+ while timeout_count:
+ state = self.get_info(instance['name'])['state']
+ if state == power_state.RUNNING:
+ LOG.info(_("Instance spawned successfully."),
+ instance=instance)
+ break
+ timeout_count.pop()
+ if len(timeout_count) == 0:
+ LOG.error(_("Instance '%s' failed to boot") %
+ instance['name'])
+ self._cleanup(instance['name'])
+ break
+ time.sleep(1)
+
+ except exception.PowerVMImageCreationFailed:
+ self._cleanup(instance['name'])
+
+ def destroy(self, instance_name):
+ """Destroy (shutdown and delete) the specified instance.
+
+ :param instance_name: Instance name.
+ """
+ try:
+ self._cleanup(instance_name)
+ except exception.PowerVMLPARInstanceNotFound:
+ LOG.warn(_("During destroy, LPAR instance '%s' was not found on "
+ "PowerVM system.") % instance_name)
+
+ def _cleanup(self, instance_name):
+ try:
+ lpar_id = self._get_instance(instance_name)['lpar_id']
+ vhost = self._operator.get_vhost_by_instance_id(lpar_id)
+ disk_name = self._operator.get_disk_name_by_vhost(vhost)
+
+ LOG.debug(_("Shutting down the instance '%s'") % instance_name)
+ self._operator.stop_lpar(instance_name)
+
+ if disk_name:
+ LOG.debug(_("Removing the logical volume '%s'") % disk_name)
+ self._operator.remove_logical_volume(disk_name)
+
+ LOG.debug(_("Deleting the LPAR instance '%s'") % instance_name)
+ self._operator.remove_lpar(instance_name)
+ except Exception:
+ LOG.exception(_("PowerVM instance cleanup failed"))
+ raise exception.PowerVMLPARInstanceCleanupFailed(
+ instance_name=instance_name)
+
+ def power_off(self, instance_name):
+ self._operator.stop(instance_name)
+
+ def power_on(self, instance_name):
+ self._operator.start(instance_name)
+
+
+class BaseOperator(object):
+ """Base operator for IVM and HMC managed systems."""
+
+ def __init__(self, connection):
+ """Constructor.
+
+ :param connection: common.Connection object with the
+ information to connect to the remote
+ ssh.
+ """
+ self._connection = common.ssh_connect(connection)
+ self.connection_data = connection
+
+ def get_lpar(self, instance_name, resource_type='lpar'):
+ """Return a LPAR object by its instance name.
+
+ :param instance_name: LPAR instance name
+ :param resource_type: the type of resources to list
+ :returns: LPAR object
+ """
+ cmd = self.command.lssyscfg('-r %s --filter "lpar_names=%s"'
+ % (resource_type, instance_name))
+ output = self.run_command(cmd)
+ if not output:
+ return None
+ lpar = LPAR.load_from_conf_data(output[0])
+ return lpar
+
+ def list_lpar_instances(self):
+ """List all existent LPAR instances names.
+
+ :returns: list -- list with instances names.
+ """
+ lpar_names = self.run_command(self.command.lssyscfg('-r lpar -F name'))
+ if not lpar_names:
+ return []
+ return lpar_names
+
+ def create_lpar(self, lpar):
+ """Receives a LPAR data object and creates a LPAR instance.
+
+ :param lpar: LPAR object
+ """
+ conf_data = lpar.to_string()
+ self.run_command(self.command.mksyscfg('-r lpar -i "%s"' % conf_data))
+
+ def start_lpar(self, instance_name):
+ """Start a LPAR instance.
+
+ :param instance_name: LPAR instance name
+ """
+ self.run_command(self.command.chsysstate('-r lpar -o on -n %s'
+ % instance_name))
+
+ def stop_lpar(self, instance_name):
+ """Stop a running LPAR.
+
+ :param instance_name: LPAR instance name
+ """
+ cmd = self.command.chsysstate('-r lpar -o shutdown --immed -n %s'
+ % instance_name)
+ self.run_command(cmd)
+
+ def remove_lpar(self, instance_name):
+ """Removes a LPAR.
+
+ :param instance_name: LPAR instance name
+ """
+ self.run_command(self.command.rmsyscfg('-r lpar -n %s'
+ % instance_name))
+
+ def get_vhost_by_instance_id(self, instance_id):
+ """Return the vhost name by the instance id.
+
+ :param instance_id: LPAR instance id
+ :returns: string -- vhost name or None in case none is found
+ """
+ instance_hex_id = '%#010x' % int(instance_id)
+ cmd = self.command.lsmap('-all -field clientid svsa -fmt :')
+ output = self.run_command(cmd)
+ vhosts = dict(item.split(':') for item in list(output))
+
+ if instance_hex_id in vhosts:
+ return vhosts[instance_hex_id]
+
+ return None
+
+ def get_virtual_eth_adapter_id(self):
+ """Virtual ethernet adapter id.
+
+ Searches for the shared ethernet adapter and returns
+ its id.
+
+ :returns: id of the virtual ethernet adapter.
+ """
+ cmd = self.command.lsmap('-all -net -field sea -fmt :')
+ output = self.run_command(cmd)
+ sea = output[0]
+ cmd = self.command.lsdev('-dev %s -attr pvid' % sea)
+ output = self.run_command(cmd)
+ # Returned output looks like this: ['value', '', '1']
+ if output:
+ return output[2]
+
+ return None
+
+ def get_disk_name_by_vhost(self, vhost):
+ """Returns the disk name attached to a vhost.
+
+ :param vhost: a vhost name
+ :returns: string -- disk name
+ """
+ cmd = self.command.lsmap('-vadapter %s -field backing -fmt :'
+ % vhost)
+ output = self.run_command(cmd)
+ if output:
+ return output[0]
+
+ return None
+
+ def remove_disk(self, disk_name):
+ """Removes a disk.
+
+ :param disk: a disk name
+ """
+ self.run_command(self.command.rmdev('-dev %s' % disk_name))
+
+ def create_logical_volume(self, size):
+ """Creates a logical volume with a minimum size.
+
+ :param size: size of the logical volume in bytes
+ :returns: string -- the name of the new logical volume.
+ :raises: PowerVMNoSpaceLeftOnVolumeGroup
+ """
+ vgs = self.run_command(self.command.lsvg())
+ cmd = self.command.lsvg('%s -field vgname freepps -fmt :'
+ % ' '.join(vgs))
+ output = self.run_command(cmd)
+ found_vg = None
+
+ # If it's not a multiple of 1MB we get the next
+ # multiple and use it as the megabyte_size.
+ megabyte = 1024 * 1024
+ if (size % megabyte) != 0:
+ megabyte_size = int(size / megabyte) + 1
+ else:
+ megabyte_size = size / megabyte
+
+ # Search for a volume group with enough free space for
+ # the new logical volume.
+ for vg in output:
+ # Returned output example: 'rootvg:396 (25344 megabytes)'
+ match = re.search(r'^(\w+):\d+\s\((\d+).+$', vg)
+ if match is None:
+ continue
+ vg_name, avail_size = match.groups()
+ if megabyte_size <= int(avail_size):
+ found_vg = vg_name
+ break
+
+ if not found_vg:
+ LOG.exception(_('Could not create logical volume. '
+ 'No space left on any volume group.'))
+ raise exception.PowerVMNoSpaceLeftOnVolumeGroup()
+
+ cmd = self.command.mklv('%s %sB' % (found_vg, size / 512))
+ lv_name, = self.run_command(cmd)
+ return lv_name
+
+ def remove_logical_volume(self, lv_name):
+ """Removes the lv and the connection between its associated vscsi.
+
+ :param lv_name: a logical volume name
+ """
+ cmd = self.command.rmvdev('-vdev %s -rmlv' % lv_name)
+ self.run_command(cmd)
+
+ def copy_file_to_device(self, source_path, device):
+ """Copy file to device.
+
+ :param source_path: path to input source file
+ :param device: output device name
+ """
+ cmd = 'dd if=%s of=/dev/%s bs=1024k' % (source_path, device)
+ self.run_command_as_root(cmd)
+
+ def copy_image_file(self, source_path, remote_path):
+ """Copy file to VIOS, decompress it, and return its new size and name.
+
+ :param source_path: source file path
+ :param remote_path remote file path
+ """
+ # Calculate source image checksum
+ hasher = hashlib.md5()
+ block_size = 0x10000
+ img_file = file(source_path, 'r')
+ buf = img_file.read(block_size)
+ while len(buf) > 0:
+ hasher.update(buf)
+ buf = img_file.read(block_size)
+ source_cksum = hasher.hexdigest()
+
+ comp_path = remote_path + os.path.basename(source_path)
+ uncomp_path = comp_path.rstrip(".gz")
+ final_path = "%s.%s" % (uncomp_path, source_cksum)
+
+ # Check whether the uncompressed image is already on IVM
+ output = self.run_command("ls %s" % final_path, check_exit_code=False)
+
+ # If the image does not exist already
+ if not len(output):
+ # Copy file to IVM
+ common.ftp_put_command(self.connection_data, source_path,
+ remote_path)
+
+ # Verify image file checksums match
+ cmd = ("/usr/bin/csum -h MD5 %s |"
+ "/usr/bin/awk '{print $1}'" % comp_path)
+ output = self.run_command_as_root(cmd)
+ if not len(output):
+ LOG.exception("Unable to get checksum")
+ raise exception.PowerVMFileTransferFailed()
+ if source_cksum != output[0]:
+ LOG.exception("Image checksums do not match")
+ raise exception.PowerVMFileTransferFailed()
+
+ # Unzip the image
+ cmd = "/usr/bin/gunzip %s" % comp_path
+ output = self.run_command_as_root(cmd)
+
+ # Remove existing image file
+ cmd = "/usr/bin/rm -f %s.*" % uncomp_path
+ output = self.run_command_as_root(cmd)
+
+ # Rename unzipped image
+ cmd = "/usr/bin/mv %s %s" % (uncomp_path, final_path)
+ output = self.run_command_as_root(cmd)
+
+ # Remove compressed image file
+ cmd = "/usr/bin/rm -f %s" % comp_path
+ output = self.run_command_as_root(cmd)
+
+ # Calculate file size in multiples of 512 bytes
+ output = self.run_command("ls -o %s|awk '{print $4}'"
+ % final_path, check_exit_code=False)
+ if len(output):
+ size = int(output[0])
+ else:
+ LOG.exception("Uncompressed image file not found")
+ raise exception.PowerVMFileTransferFailed()
+ if (size % 512 != 0):
+ size = (int(size / 512) + 1) * 512
+
+ return final_path, size
+
+ def run_cfg_dev(self, device_name):
+ """Run cfgdev command for a specific device.
+
+ :param device_name: device name the cfgdev command will run.
+ """
+ cmd = self.command.cfgdev('-dev %s' % device_name)
+ self.run_command(cmd)
+
+ def attach_disk_to_vhost(self, disk, vhost):
+ """Attach disk name to a specific vhost.
+
+ :param disk: the disk name
+ :param vhost: the vhost name
+ """
+ cmd = self.command.mkvdev('-vdev %s -vadapter %s') % (disk, vhost)
+ self.run_command(cmd)
+
+ def get_memory_info(self):
+ """Get memory info.
+
+ :returns: tuple - memory info (total_mem, avail_mem)
+ """
+ cmd = self.command.lshwres(
+ '-r mem --level sys -F configurable_sys_mem,curr_avail_sys_mem')
+ output = self.run_command(cmd)
+ total_mem, avail_mem = output[0].split(',')
+ return {'total_mem': int(total_mem),
+ 'avail_mem': int(avail_mem)}
+
+ def get_cpu_info(self):
+ """Get CPU info.
+
+ :returns: tuple - cpu info (total_procs, avail_procs)
+ """
+ cmd = self.command.lshwres(
+ '-r proc --level sys -F '
+ 'configurable_sys_proc_units,curr_avail_sys_proc_units')
+ output = self.run_command(cmd)
+ total_procs, avail_procs = output[0].split(',')
+ return {'total_procs': float(total_procs),
+ 'avail_procs': float(avail_procs)}
+
+ def get_disk_info(self):
+ """Get the disk usage information.
+
+ :returns: tuple - disk info (disk_total, disk_used, disk_avail)
+ """
+ vgs = self.run_command(self.command.lsvg())
+ (disk_total, disk_used, disk_avail) = [0, 0, 0]
+ for vg in vgs:
+ cmd = self.command.lsvg('%s -field totalpps usedpps freepps -fmt :'
+ % vg)
+ output = self.run_command(cmd)
+ # Output example:
+ # 1271 (10168 megabytes):0 (0 megabytes):1271 (10168 megabytes)
+ (d_total, d_used, d_avail) = re.findall(r'(\d+) megabytes',
+ output[0])
+ disk_total += int(d_total)
+ disk_used += int(d_used)
+ disk_avail += int(d_avail)
+
+ return {'disk_total': disk_total,
+ 'disk_used': disk_used,
+ 'disk_avail': disk_avail}
+
+ def run_command(self, cmd, check_exit_code=True):
+ """Run a remote command using an active ssh connection.
+
+ :param command: String with the command to run.
+ """
+ stdout, stderr = utils.ssh_execute(self._connection, cmd,
+ check_exit_code=check_exit_code)
+ return stdout.strip().splitlines()
+
+ def run_command_as_root(self, command, check_exit_code=True):
+ """Run a remote command as root using an active ssh connection.
+
+ :param command: List of commands.
+ """
+ stdout, stderr = common.ssh_command_as_root(
+ self._connection, command, check_exit_code=check_exit_code)
+ return stdout.read().splitlines()
+
+
+class IVMOperator(BaseOperator):
+ """Integrated Virtualization Manager (IVM) Operator.
+
+ Runs specific commands on an IVM managed system.
+ """
+
+ def __init__(self, ivm_connection):
+ self.command = command.IVMCommand()
+ BaseOperator.__init__(self, ivm_connection)