Files
watcher/watcher/applier/actions/migration.py
Yumeng_Bao 1451d9c134 Replace voluptuous with JSONSchema to validate migration action
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the migration action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: I02bff5db9bd06567bcc33b61a316c42c805bb20e
2017-06-12 17:40:07 +08:00

194 lines
6.8 KiB
Python

# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# 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 jsonschema
from oslo_log import log
from watcher._i18n import _
from watcher.applier.actions import base
from watcher.common import exception
from watcher.common import nova_helper
LOG = log.getLogger(__name__)
class Migrate(base.BaseAction):
"""Migrates a server to a destination nova-compute host
This action will allow you to migrate a server to another compute
destination host.
Migration type 'live' can only be used for migrating active VMs.
Migration type 'cold' can be used for migrating non-active VMs
as well active VMs, which will be shut down while migrating.
The action schema is::
schema = Schema({
'resource_id': str, # should be a UUID
'migration_type': str, # choices -> "live", "cold"
'destination_node': str,
'source_node': str,
})
The `resource_id` is the UUID of the server to migrate.
The `source_node` and `destination_node` parameters are respectively the
source and the destination compute hostname (list of available compute
hosts is returned by this command: ``nova service-list --binary
nova-compute``).
"""
# input parameters constants
MIGRATION_TYPE = 'migration_type'
LIVE_MIGRATION = 'live'
COLD_MIGRATION = 'cold'
DESTINATION_NODE = 'destination_node'
SOURCE_NODE = 'source_node'
@property
def schema(self):
return {
'type': 'object',
'properties': {
'destination_node': {
'type': 'string',
"minLength": 1
},
'migration_type': {
'type': 'string',
"enum": ["live", "cold"]
},
'resource_id': {
'type': 'string',
"minlength": 1,
"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}$")
},
'source_node': {
'type': 'string',
"minLength": 1
}
},
'required': ['destination_node', 'migration_type',
'resource_id', 'source_node'],
'additionalProperties': False,
}
def validate_parameters(self):
try:
jsonschema.validate(self.input_parameters, self.schema)
return True
except jsonschema.ValidationError as e:
raise e
@property
def instance_uuid(self):
return self.resource_id
@property
def migration_type(self):
return self.input_parameters.get(self.MIGRATION_TYPE)
@property
def destination_node(self):
return self.input_parameters.get(self.DESTINATION_NODE)
@property
def source_node(self):
return self.input_parameters.get(self.SOURCE_NODE)
def _live_migrate_instance(self, nova, destination):
result = None
try:
result = nova.live_migrate_instance(instance_id=self.instance_uuid,
dest_hostname=destination)
except nova_helper.nvexceptions.ClientException as e:
if e.code == 400:
LOG.debug("Live migration of instance %s failed. "
"Trying to live migrate using block migration."
% self.instance_uuid)
result = nova.live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination,
block_migration=True)
else:
LOG.debug("Nova client exception occurred while live "
"migrating instance %s.Exception: %s" %
(self.instance_uuid, e))
except Exception:
LOG.critical("Unexpected error occurred. Migration failed for "
"instance %s. Leaving instance on previous "
"host.", self.instance_uuid)
return result
def _cold_migrate_instance(self, nova, destination):
result = None
try:
result = nova.watcher_non_live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination)
except Exception as exc:
LOG.exception(exc)
LOG.critical("Unexpected error occurred. Migration failed for "
"instance %s. Leaving instance on previous "
"host.", self.instance_uuid)
return result
def migrate(self, destination):
nova = nova_helper.NovaHelper(osc=self.osc)
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
destination)
instance = nova.find_instance(self.instance_uuid)
if instance:
if self.migration_type == self.LIVE_MIGRATION:
return self._live_migrate_instance(nova, destination)
elif self.migration_type == self.COLD_MIGRATION:
return self._cold_migrate_instance(nova, destination)
else:
raise exception.Invalid(
message=(_("Migration of type '%(migration_type)s' is not "
"supported.") %
{'migration_type': self.migration_type}))
else:
raise exception.InstanceNotFound(name=self.instance_uuid)
def execute(self):
return self.migrate(destination=self.destination_node)
def revert(self):
return self.migrate(destination=self.source_node)
def abort(self):
# TODO(adisky): implement abort for migration
LOG.warning("Abort for migration not implemented")
def pre_condition(self):
# TODO(jed): check if the instance exists / check if the instance is on
# the source_node
pass
def post_condition(self):
# TODO(jed): check extra parameters (network response, etc.)
pass
def get_description(self):
"""Description of the action"""
return "Moving a VM instance from source_node to destination_node"