
This commit adds some info about the queue usage to each project Change-Id: If14ca606148b0dbd2b22bfa470196b2db7c47d07 Sem-Ver: bugfix
1133 lines
41 KiB
Python
1133 lines
41 KiB
Python
import common.utils as utils
|
|
import eventlet
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
import requests
|
|
|
|
from common.block_device import BlockDeviceMapping
|
|
from common.compute import Compute
|
|
from common.flavor import Flavor
|
|
from common.hypervisor import Hypervisor
|
|
from common.messaging import AMQP
|
|
from common.quota import Quota
|
|
from common.request import Request
|
|
from common.server import Server
|
|
from oslo_config import cfg
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from synergy.common.manager import Manager
|
|
from synergy.exception import SynergyError
|
|
|
|
__author__ = "Lisa Zangrando"
|
|
__email__ = "lisa.zangrando[AT]pd.infn.it"
|
|
__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud
|
|
All Rights Reserved
|
|
|
|
Licensed under the Apache License, Version 2.0;
|
|
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."""
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class ServerEventHandler(object):
|
|
|
|
def __init__(self, nova_manager):
|
|
super(ServerEventHandler, self).__init__()
|
|
|
|
self.nova_manager = nova_manager
|
|
|
|
def _makeServer(self, server_info):
|
|
if not server_info:
|
|
return
|
|
|
|
flavor = Flavor()
|
|
flavor.setMemory(server_info["memory_mb"])
|
|
flavor.setVCPUs(server_info["vcpus"])
|
|
flavor.setStorage(server_info["root_gb"])
|
|
|
|
if "instance_type" in server_info:
|
|
flavor.setName(server_info["instance_type"])
|
|
|
|
server = Server()
|
|
server.setFlavor(flavor)
|
|
server.setUserId(server_info["user_id"])
|
|
server.setMetadata(server_info["metadata"])
|
|
server.setDeletedAt(server_info["deleted_at"])
|
|
server.setTerminatedAt(server_info["terminated_at"])
|
|
|
|
if "host" in server_info:
|
|
server.setHost(server_info["host"])
|
|
|
|
if "uuid" in server_info:
|
|
server.setId(server_info["uuid"])
|
|
elif "instance_id" in server_info:
|
|
server.setId(server_info["instance_id"])
|
|
|
|
if "project_id" in server_info:
|
|
server.setProjectId(server_info["project_id"])
|
|
elif "tenant_id" in server_info:
|
|
server.setProjectId(server_info["tenant_id"])
|
|
|
|
if "vm_state" in server_info:
|
|
server.setState(server_info["vm_state"])
|
|
elif "state" in server_info:
|
|
server.setState(server_info["state"])
|
|
|
|
return server
|
|
|
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
|
LOG.debug("Notification INFO: event_type=%s payload=%s"
|
|
% (event_type, payload))
|
|
|
|
if payload is None or "state" not in payload:
|
|
return
|
|
|
|
state = payload["state"]
|
|
|
|
event_types = ["compute.instance.create.end",
|
|
"compute.instance.delete.end",
|
|
"compute.instance.update",
|
|
"scheduler.run_instance"]
|
|
|
|
if event_type not in event_types:
|
|
return
|
|
|
|
server_info = None
|
|
|
|
if event_type == "scheduler.run_instance":
|
|
server_info = payload["request_spec"]["instance_type"]
|
|
else:
|
|
server_info = payload
|
|
|
|
server = self._makeServer(server_info)
|
|
|
|
self.nova_manager.notify(event_type="SERVER_EVENT", server=server,
|
|
event=event_type, state=state)
|
|
|
|
def warn(self, ctxt, publisher_id, event_type, payload, metadata):
|
|
LOG.debug("Notification WARN: event_type=%s, payload=%s metadata=%s"
|
|
% (event_type, payload, metadata))
|
|
|
|
def error(self, ctxt, publisher_id, event_type, payload, metadata):
|
|
LOG.debug("Notification ERROR: event_type=%s, payload=%s metadata=%s"
|
|
% (event_type, payload, metadata))
|
|
|
|
|
|
class NovaConductorComputeAPI(object):
|
|
|
|
def __init__(self, synergy_topic, conductor_topic, nova_manager, msg):
|
|
self.nova_manager = nova_manager
|
|
|
|
self.target = msg.getTarget(topic=synergy_topic,
|
|
namespace="compute_task",
|
|
version="1.16")
|
|
|
|
self.client = msg.getRPCClient(
|
|
target=msg.getTarget(topic=conductor_topic,
|
|
namespace="compute_task",
|
|
version="1.10"))
|
|
|
|
def build_instances(self, context, instances, image, filter_properties,
|
|
admin_password, injected_files, requested_networks,
|
|
security_groups, block_device_mapping=None,
|
|
legacy_bdm=True):
|
|
for instance in instances:
|
|
data = {'instances': [instance],
|
|
'image': image,
|
|
'filter_properties': filter_properties,
|
|
'admin_password': admin_password,
|
|
'injected_files': injected_files,
|
|
'requested_networks': requested_networks,
|
|
'security_groups': security_groups,
|
|
'block_device_mapping': block_device_mapping,
|
|
'legacy_bdm': legacy_bdm}
|
|
|
|
req = {"context": context, "data": data,
|
|
"action": "build_instances"}
|
|
try:
|
|
request = Request.fromDict(req)
|
|
|
|
self.nova_manager.notify(event_type="SERVER_CREATE",
|
|
request=request)
|
|
except Exception as ex:
|
|
LOG.info(ex)
|
|
|
|
def schedule_and_build_instances(self, context, build_requests,
|
|
request_specs, image,
|
|
admin_password, injected_files,
|
|
requested_networks, block_device_mapping):
|
|
index = 0
|
|
|
|
for build_request in build_requests:
|
|
request_spec = request_specs[index]
|
|
|
|
index += 1
|
|
|
|
data = {'build_requests': [build_request],
|
|
'request_specs': [request_spec],
|
|
'image': image,
|
|
'admin_password': admin_password,
|
|
'injected_files': injected_files,
|
|
'requested_networks': requested_networks,
|
|
'block_device_mapping': block_device_mapping}
|
|
|
|
req = {"context": context, "data": data,
|
|
"action": "schedule_and_build_instances"}
|
|
|
|
request = Request.fromDict(req)
|
|
|
|
self.nova_manager.notify(event_type="SERVER_CREATE",
|
|
request=request)
|
|
|
|
def build_instance(self, context, action, data):
|
|
try:
|
|
cctxt = self.client.prepare()
|
|
cctxt.cast(context, action, **data)
|
|
except Exception as ex:
|
|
LOG.info(ex)
|
|
|
|
def migrate_server(self, context, **kwargs):
|
|
cctxt = self.client.prepare()
|
|
return cctxt.call(context, 'migrate_server', **kwargs)
|
|
|
|
def unshelve_instance(self, context, **kwargs):
|
|
cctxt = self.client.prepare()
|
|
cctxt.cast(context, 'unshelve_instance', **kwargs)
|
|
|
|
def rebuild_instance(self, ctxt, **kwargs):
|
|
cctxt = self.client.prepare()
|
|
cctxt.cast(ctxt, 'rebuild_instance', **kwargs)
|
|
|
|
def resize_instance(self, ctxt, **kwargs):
|
|
cctxt = self.client.prepare()
|
|
cctxt.cast(ctxt, 'resize_instance', **kwargs)
|
|
|
|
|
|
class NovaManager(Manager):
|
|
|
|
def __init__(self):
|
|
super(NovaManager, self).__init__("NovaManager")
|
|
|
|
self.config_opts = [
|
|
cfg.StrOpt("amqp_url",
|
|
help="the amqp transport url",
|
|
default=None,
|
|
required=False),
|
|
cfg.StrOpt("amqp_exchange",
|
|
help="the amqp exchange",
|
|
default="nova",
|
|
required=False),
|
|
cfg.StrOpt("amqp_backend",
|
|
help="the amqp backend tpye (e.g. rabbit, qpid)",
|
|
default=None,
|
|
required=False),
|
|
cfg.ListOpt("amqp_hosts",
|
|
help="AMQP HA cluster host:port pairs",
|
|
default=None,
|
|
required=False),
|
|
cfg.StrOpt("amqp_host",
|
|
help="the amqp host name",
|
|
default="localhost",
|
|
required=False),
|
|
cfg.IntOpt("amqp_port",
|
|
help="the amqp listening port",
|
|
default=5672,
|
|
required=False),
|
|
cfg.StrOpt("amqp_user",
|
|
help="the amqp user",
|
|
default=None,
|
|
required=False),
|
|
cfg.StrOpt("amqp_password",
|
|
help="the amqp password",
|
|
default=None,
|
|
required=False),
|
|
cfg.StrOpt("amqp_virt_host",
|
|
help="the amqp virtual host",
|
|
default="/",
|
|
required=False),
|
|
cfg.StrOpt("synergy_topic",
|
|
help="the Synergy topic",
|
|
default="synergy",
|
|
required=False),
|
|
cfg.StrOpt("notification_topic",
|
|
help="the notifiction topic",
|
|
default="notifications",
|
|
required=False),
|
|
cfg.StrOpt("conductor_topic",
|
|
help="the conductor topic",
|
|
default="conductor",
|
|
required=False),
|
|
cfg.StrOpt("compute_topic",
|
|
help="the compute topic",
|
|
default="compute",
|
|
required=False),
|
|
cfg.StrOpt("scheduler_topic",
|
|
help="the scheduler topic",
|
|
default="scheduler",
|
|
required=False),
|
|
cfg.StrOpt("metadata_proxy_shared_secret",
|
|
help="the metadata proxy shared secret",
|
|
default=None,
|
|
required=True),
|
|
cfg.FloatOpt("cpu_allocation_ratio",
|
|
help="the cpu allocation ratio",
|
|
default=float(16),
|
|
required=False),
|
|
cfg.FloatOpt("ram_allocation_ratio",
|
|
help="the ram allocation ratio",
|
|
default=float(1.5),
|
|
required=False),
|
|
cfg.StrOpt("db_connection",
|
|
help="the NOVA database connection",
|
|
default=None,
|
|
required=True),
|
|
cfg.StrOpt("host",
|
|
help="the host name",
|
|
default="localhost",
|
|
required=False),
|
|
cfg.IntOpt("timeout",
|
|
help="set the http connection timeout",
|
|
default=60,
|
|
required=False),
|
|
cfg.StrOpt("ssl_ca_file",
|
|
help="set the PEM encoded Certificate Authority to "
|
|
"use when verifying HTTPs connections",
|
|
default=None,
|
|
required=False),
|
|
cfg.StrOpt("ssl_cert_file",
|
|
help="set the SSL client certificate (PEM encoded)",
|
|
default=None,
|
|
required=False)
|
|
]
|
|
|
|
def setup(self):
|
|
eventlet.monkey_patch(os=False)
|
|
|
|
self.ssl_ca_file = CONF.NovaManager.ssl_ca_file
|
|
self.ssl_cert_file = CONF.NovaManager.ssl_cert_file
|
|
self.timeout = CONF.NovaManager.timeout
|
|
|
|
if self.getManager("KeystoneManager") is None:
|
|
raise SynergyError("KeystoneManager not found!")
|
|
|
|
if self.getManager("SchedulerManager") is None:
|
|
raise SynergyError("SchedulerManager not found!")
|
|
|
|
self.keystone_manager = self.getManager("KeystoneManager")
|
|
|
|
amqp_url = self.getParameter("amqp_url")
|
|
|
|
amqp_backend = self.getParameter("amqp_backend")
|
|
|
|
amqp_hosts = self.getParameter("amqp_hosts")
|
|
|
|
amqp_host = self.getParameter("amqp_host")
|
|
|
|
amqp_port = self.getParameter("amqp_port")
|
|
|
|
amqp_user = self.getParameter("amqp_user")
|
|
|
|
amqp_password = self.getParameter("amqp_password")
|
|
|
|
amqp_virt_host = self.getParameter("amqp_virt_host")
|
|
|
|
amqp_exchange = self.getParameter("amqp_exchange")
|
|
|
|
db_connection = self.getParameter("db_connection", fallback=True)
|
|
|
|
host = self.getParameter("host")
|
|
|
|
synergy_topic = self.getParameter("synergy_topic")
|
|
|
|
notification_topic = self.getParameter("notification_topic")
|
|
|
|
conductor_topic = self.getParameter("conductor_topic")
|
|
|
|
self.getParameter("metadata_proxy_shared_secret", fallback=True)
|
|
|
|
if not amqp_hosts:
|
|
amqp_hosts = ["%s:%s" % (amqp_host, amqp_port)]
|
|
try:
|
|
LOG.debug("setting up the NOVA database connection: %s"
|
|
% db_connection)
|
|
|
|
self.db_engine = create_engine(db_connection, pool_recycle=30)
|
|
|
|
self.messaging = AMQP(url=amqp_url, backend=amqp_backend,
|
|
username=amqp_user, password=amqp_password,
|
|
hosts=amqp_hosts, virt_host=amqp_virt_host,
|
|
exchange=amqp_exchange)
|
|
|
|
self.novaConductorComputeAPI = NovaConductorComputeAPI(
|
|
synergy_topic,
|
|
conductor_topic,
|
|
self,
|
|
self.messaging)
|
|
|
|
self.conductor_rpc = self.messaging.getRPCServer(
|
|
target=self.messaging.getTarget(topic=synergy_topic,
|
|
server=host),
|
|
endpoints=[self.novaConductorComputeAPI])
|
|
|
|
self.conductor_rpc.start()
|
|
|
|
self.serverEventHandler = ServerEventHandler(self)
|
|
|
|
target = self.messaging.getTarget(topic=notification_topic,
|
|
exchange=amqp_exchange)
|
|
|
|
self.listener = self.messaging.getNotificationListener(
|
|
targets=[target], endpoints=[self.serverEventHandler])
|
|
|
|
self.listener.start()
|
|
except Exception as ex:
|
|
LOG.error("Exception has occured", exc_info=1)
|
|
LOG.error("NovaManager initialization failed! %s" % (ex))
|
|
raise ex
|
|
|
|
def execute(self, command, *args, **kargs):
|
|
raise SynergyError("command %r not supported!" % command)
|
|
|
|
def task(self):
|
|
pass
|
|
|
|
def destroy(self):
|
|
pass
|
|
|
|
def getParameter(self, name, fallback=False):
|
|
result = CONF.NovaManager.get(name, None)
|
|
|
|
if result is not None:
|
|
return result
|
|
|
|
if fallback is True:
|
|
raise SynergyError("No attribute %r found in [NovaManager] "
|
|
"section of synergy.conf" % name)
|
|
else:
|
|
return None
|
|
|
|
def getUserData(self, server):
|
|
if not server:
|
|
return None
|
|
|
|
secret = CONF.NovaManager.metadata_proxy_shared_secret
|
|
|
|
if not secret:
|
|
raise SynergyError("'metadata_proxy_shared_secret' "
|
|
"attribute not defined in synergy.conf")
|
|
|
|
digest = hmac.new(secret, server.getId(), hashlib.sha256).hexdigest()
|
|
|
|
self.keystone_manager.authenticate()
|
|
token = self.keystone_manager.getToken()
|
|
service = token.getService("nova")
|
|
|
|
if not service:
|
|
raise SynergyError("nova service not found!")
|
|
|
|
endpoint = service.getEndpoint("public")
|
|
|
|
if not endpoint:
|
|
raise SynergyError("nova endpoint not found!")
|
|
|
|
url = endpoint.getURL()
|
|
url = url[:url.rfind(":") + 1] + "8775/openstack/2015-10-15/user_data"
|
|
|
|
headers = {"Content-Type": "application/text",
|
|
"Accept": "application/text",
|
|
"User-Agent": "synergy",
|
|
"x-instance-id": server.getId(),
|
|
"x-tenant-id": server.getProjectId(),
|
|
"x-instance-id-signature": digest}
|
|
|
|
request = requests.get(url, headers=headers,
|
|
timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
|
|
if request.status_code != requests.codes.ok:
|
|
if request.status_code == 404:
|
|
return None
|
|
elif request.status_code == 403:
|
|
if "Invalid proxy request signature" in request._content:
|
|
raise SynergyError("cannot retrieve the 'userdata' value: "
|
|
"check the 'metadata_proxy_shared_"
|
|
"secret' attribute value")
|
|
else:
|
|
request.raise_for_status()
|
|
else:
|
|
request.raise_for_status()
|
|
|
|
if request.text:
|
|
return request.text
|
|
else:
|
|
return None
|
|
|
|
def getFlavors(self):
|
|
url = "flavors/detail"
|
|
|
|
try:
|
|
response_data = self.getResource(url, method="GET")
|
|
except requests.exceptions.HTTPError as ex:
|
|
response = ex.response.json()
|
|
raise SynergyError("error on retrieving the flavors list: %s"
|
|
% response)
|
|
|
|
flavors = []
|
|
|
|
if response_data:
|
|
for flavor_data in response_data["flavors"]:
|
|
flavor = Flavor()
|
|
flavor.setId(flavor_data["id"])
|
|
flavor.setName(flavor_data["name"])
|
|
flavor.setVCPUs(flavor_data["vcpus"])
|
|
flavor.setMemory(flavor_data["ram"])
|
|
flavor.setStorage(flavor_data["disk"])
|
|
|
|
flavors.append(flavor)
|
|
|
|
return flavors
|
|
|
|
def getFlavor(self, id):
|
|
try:
|
|
response_data = self.getResource("flavors/" + id, "GET")
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on retrieving the flavor info (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
flavor = None
|
|
|
|
if response_data:
|
|
flavor_data = response_data["flavor"]
|
|
|
|
flavor = Flavor()
|
|
flavor.setId(flavor_data["id"])
|
|
flavor.setName(flavor_data["name"])
|
|
flavor.setVCPUs(flavor_data["vcpus"])
|
|
flavor.setMemory(flavor_data["ram"])
|
|
flavor.setStorage(flavor_data["disk"])
|
|
|
|
return flavor
|
|
|
|
def getServers(self, detail=False, status=None):
|
|
params = {}
|
|
if status:
|
|
params["status"] = status
|
|
|
|
url = "servers/detail"
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET", params)
|
|
except requests.exceptions.HTTPError as ex:
|
|
response = ex.response.json()
|
|
raise SynergyError("error on retrieving the servers list"
|
|
": %s" % (id, response))
|
|
|
|
servers = []
|
|
|
|
if response_data:
|
|
for server_data in response_data["servers"]:
|
|
server = Server()
|
|
server.setId(server_data["id"])
|
|
server.setName(server_data["name"])
|
|
server.setKeyName(server_data["key_name"])
|
|
server.setMetadata(server_data["metadata"])
|
|
server.setState(server_data["OS-EXT-STS:vm_state"])
|
|
server.setUserId(server_data["user_id"])
|
|
server.setProjectId(server_data["tenant_id"])
|
|
server.setCreatedAt(server_data["created"])
|
|
server.setUpdatedAt(server_data.get("updated", None))
|
|
server.setLaunchedAt(
|
|
server_data.get("OS-SRV-USG:launched_at", None))
|
|
server.setTerminatedAt(
|
|
server_data.get("OS-SRV-USG:terminated_at", None))
|
|
|
|
if "user_data" in server_data:
|
|
user_data = server_data["user_data"]
|
|
server.setUserData(utils.decodeBase64(user_data))
|
|
|
|
if detail:
|
|
server.setFlavor(self.getFlavor(
|
|
server_data["flavor"]["id"]))
|
|
|
|
servers.append(server)
|
|
|
|
return servers
|
|
|
|
def getServer(self, id, detail=False):
|
|
try:
|
|
response_data = self.getResource("servers/" + id, "GET")
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on retrieving the server info (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
server = None
|
|
|
|
if response_data:
|
|
server_data = response_data["server"]
|
|
|
|
server = Server()
|
|
server.setId(server_data["id"])
|
|
server.setName(server_data["name"])
|
|
server.setKeyName(server_data["key_name"])
|
|
server.setMetadata(server_data["metadata"])
|
|
server.setState(server_data["OS-EXT-STS:vm_state"])
|
|
server.setUserId(server_data["user_id"])
|
|
server.setProjectId(server_data["tenant_id"])
|
|
server.setCreatedAt(server_data["created"])
|
|
server.setUpdatedAt(server_data.get("updated", None))
|
|
server.setLaunchedAt(
|
|
server_data.get("OS-SRV-USG:launched_at", None))
|
|
server.setTerminatedAt(
|
|
server_data.get("OS-SRV-USG:terminated_at", None))
|
|
|
|
if "user_data" in server_data:
|
|
user_data = server_data["user_data"]
|
|
server.setUserData(utils.decodeBase64(user_data))
|
|
|
|
if detail:
|
|
server.setFlavor(self.getFlavor(server_data["flavor"]["id"]))
|
|
|
|
return server
|
|
|
|
def buildServer(self, request):
|
|
self.novaConductorComputeAPI.build_instance(
|
|
request.getContext(),
|
|
request.getAction(),
|
|
request.getData())
|
|
|
|
def deleteServer(self, server):
|
|
if not server:
|
|
return
|
|
|
|
id = server.getId()
|
|
url = "servers/%s" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "DELETE")
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on deleting the server (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
if response_data:
|
|
response_data = response_data["server"]
|
|
|
|
return response_data
|
|
|
|
def startServer(self, server):
|
|
if not server:
|
|
return
|
|
|
|
id = server.getId()
|
|
data = {"os-start": None}
|
|
url = "servers/%s/action" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "POST", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on starting the server %s"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
if response_data:
|
|
response_data = response_data["server"]
|
|
|
|
return response_data
|
|
|
|
def stopServer(self, server):
|
|
if not server:
|
|
return
|
|
|
|
id = server.getId()
|
|
data = {"os-stop": None}
|
|
url = "servers/%s/action" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "POST", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on stopping the server info (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
if response_data:
|
|
response_data = response_data["server"]
|
|
|
|
return response_data
|
|
|
|
def getHosts(self):
|
|
data = {}
|
|
url = "os-hosts"
|
|
# /%s" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
response = ex.response.json()
|
|
raise SynergyError("error on retrieving the hypervisors list: %s"
|
|
% response["badRequest"]["message"])
|
|
|
|
if response_data:
|
|
response_data = response_data["hosts"]
|
|
|
|
return response_data
|
|
|
|
def getHost(self, name):
|
|
data = {}
|
|
url = "os-hosts/%s" % name
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
response = ex.response.json()
|
|
raise SynergyError("error on retrieving the hypervisor info (id=%r"
|
|
"): %s" % (id,
|
|
response["badRequest"]["message"]))
|
|
|
|
if response_data:
|
|
response_data = response_data["host"]
|
|
|
|
return response_data
|
|
|
|
def getHypervisors(self):
|
|
data = {"os-stop": None}
|
|
url = "os-hypervisors/detail"
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
LOG.info(ex)
|
|
response = ex.response.json()
|
|
raise SynergyError("error on retrieving the hypervisors list: %s"
|
|
% response["badRequest"]["message"])
|
|
|
|
hypervisors = []
|
|
|
|
if response_data:
|
|
hypervisors_data = response_data["hypervisors"]
|
|
|
|
for hypervisor_data in hypervisors_data:
|
|
hypervisor = Hypervisor()
|
|
hypervisor.setId(hypervisor_data["id"])
|
|
hypervisor.setIP(hypervisor_data["host_ip"])
|
|
hypervisor.setName(hypervisor_data["hypervisor_hostname"])
|
|
hypervisor.setType(hypervisor_data["hypervisor_type"])
|
|
hypervisor.setState(hypervisor_data["state"])
|
|
hypervisor.setStatus(hypervisor_data["status"])
|
|
hypervisor.setWorkload(hypervisor_data["current_workload"])
|
|
hypervisor.setVMs(hypervisor_data["running_vms"])
|
|
hypervisor.setVCPUs(hypervisor_data["vcpus"])
|
|
hypervisor.setVCPUs(hypervisor_data["vcpus_used"], used=True)
|
|
hypervisor.setMemory(hypervisor_data["memory_mb"])
|
|
hypervisor.setMemory(hypervisor_data["memory_mb_used"], used=True)
|
|
hypervisor.setStorage(hypervisor_data["local_gb"])
|
|
hypervisor.setStorage(hypervisor_data["local_gb_used"], used=True)
|
|
|
|
hypervisors.append(hypervisor)
|
|
|
|
return hypervisors
|
|
|
|
def getHypervisor(self, id):
|
|
data = {"os-stop": None}
|
|
url = "os-hypervisors/%s" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET", data)
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on retrieving the hypervisor info (id=%r"
|
|
"): %s" % (id, ex.response.json()))
|
|
|
|
hypervisor = None
|
|
|
|
if response_data:
|
|
hypervisor_data = response_data["hypervisor"]
|
|
|
|
hypervisor = Hypervisor()
|
|
hypervisor.setId(hypervisor_data["id"])
|
|
hypervisor.setIP(hypervisor_data["host_ip"])
|
|
hypervisor.setName(hypervisor_data["hypervisor_hostname"])
|
|
hypervisor.setType(hypervisor_data["hypervisor_type"])
|
|
hypervisor.setState(hypervisor_data["state"])
|
|
hypervisor.setStatus(hypervisor_data["status"])
|
|
hypervisor.setWorkload(hypervisor_data["current_workload"])
|
|
hypervisor.setVMs(hypervisor_data["running_vms"])
|
|
hypervisor.setVCPUs(hypervisor_data["vcpus"])
|
|
hypervisor.setVCPUs(hypervisor_data["vcpus_used"], used=True)
|
|
hypervisor.setMemory(hypervisor_data["memory_mb"])
|
|
hypervisor.setMemory(hypervisor_data["memory_mb_used"], used=True)
|
|
hypervisor.setStorage(hypervisor_data["local_gb"])
|
|
hypervisor.setStorage(hypervisor_data["local_gb_used"], used=True)
|
|
|
|
return hypervisor
|
|
|
|
def getQuota(self, id=None, is_class=False, defaults=False):
|
|
if defaults:
|
|
try:
|
|
url = "os-quota-sets/defaults"
|
|
response_data = self.getResource(url, "GET")
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on retrieving the quota defaults"
|
|
": %s" % ex.response.json())
|
|
elif id is not None:
|
|
if is_class:
|
|
url = "os-quota-class-sets/%s" % id
|
|
else:
|
|
url = "os-quota-sets/%s" % id
|
|
|
|
try:
|
|
response_data = self.getResource(url, "GET")
|
|
|
|
if is_class:
|
|
quota_data = response_data["quota_class_set"]
|
|
else:
|
|
quota_data = response_data["quota_set"]
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on retrieving the quota info (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
else:
|
|
raise SynergyError("wrong arguments")
|
|
|
|
quota = None
|
|
|
|
if quota_data:
|
|
quota = Quota()
|
|
quota.setId(id)
|
|
quota.setSize("vcpus", quota_data["cores"])
|
|
quota.setSize("memory", quota_data["ram"])
|
|
quota.setSize("instances", quota_data["instances"])
|
|
|
|
return quota
|
|
|
|
def updateQuota(self, quota, is_class=False):
|
|
if is_class:
|
|
url = "os-quota-class-sets/%s" % quota.getId()
|
|
|
|
qs = {"quota_class_set": {"cores": quota.getSize("vcpus"),
|
|
"ram": quota.getSize("memory"),
|
|
"instances": quota.getSize("instances")}}
|
|
else:
|
|
url = "os-quota-sets/%s" % quota.getId()
|
|
|
|
qs = {"quota_set": {"force": True,
|
|
"cores": quota.getSize("vcpus"),
|
|
"ram": quota.getSize("memory"),
|
|
"instances": quota.getSize("instances")}}
|
|
|
|
try:
|
|
self.getResource(url, "PUT", qs)
|
|
except requests.exceptions.HTTPError as ex:
|
|
raise SynergyError("error on updating the quota info (id=%r)"
|
|
": %s" % (id, ex.response.json()))
|
|
|
|
def getResource(self, resource, method, data=None):
|
|
self.keystone_manager.authenticate()
|
|
token = self.keystone_manager.getToken()
|
|
service = token.getService("nova")
|
|
|
|
if not service:
|
|
raise SynergyError("nova service not found!")
|
|
|
|
endpoint = service.getEndpoint("public")
|
|
|
|
if not endpoint:
|
|
raise SynergyError("nova endpoint not found!")
|
|
|
|
url = endpoint.getURL() + "/" + resource
|
|
|
|
headers = {"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
"User-Agent": "python-novaclient",
|
|
"X-Auth-Project-Id": token.getProject().getName(),
|
|
"X-Auth-Token": token.getId()}
|
|
|
|
if method == "GET":
|
|
request = requests.get(url, headers=headers,
|
|
params=data, timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
elif method == "POST":
|
|
request = requests.post(url,
|
|
headers=headers,
|
|
data=json.dumps(data),
|
|
timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
elif method == "PUT":
|
|
request = requests.put(url,
|
|
headers=headers,
|
|
data=json.dumps(data),
|
|
timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
elif method == "HEAD":
|
|
request = requests.head(url,
|
|
headers=headers,
|
|
data=json.dumps(data),
|
|
timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
elif method == "DELETE":
|
|
request = requests.delete(url,
|
|
headers=headers,
|
|
data=json.dumps(data),
|
|
timeout=self.timeout,
|
|
verify=self.ssl_ca_file,
|
|
cert=self.ssl_cert_file)
|
|
else:
|
|
raise SynergyError("wrong HTTP method: %s" % method)
|
|
|
|
if request.status_code != requests.codes.ok:
|
|
request.raise_for_status()
|
|
|
|
if request.text:
|
|
return request.json()
|
|
else:
|
|
return None
|
|
|
|
def getTarget(self, topic, exchange=None, namespace=None,
|
|
version=None, server=None):
|
|
return self.messaging.getTarget(topic=topic,
|
|
namespace=namespace,
|
|
exchange=exchange,
|
|
version=version,
|
|
server=server)
|
|
|
|
def getRPCClient(self, target, version_cap=None, serializer=None):
|
|
return self.messaging.getRPCClient(target,
|
|
version_cap=version_cap,
|
|
serializer=serializer)
|
|
|
|
def getRPCServer(self, target, endpoints, serializer=None):
|
|
return self.messaging.getRPCServer(target,
|
|
endpoints,
|
|
serializer=serializer)
|
|
|
|
def getNotificationListener(self, targets, endpoints):
|
|
return self.messaging.getNotificationListener(targets, endpoints)
|
|
|
|
def getProjectUsage(self, prj_id, from_date, to_date):
|
|
usage = {}
|
|
connection = self.db_engine.connect()
|
|
|
|
try:
|
|
QUERY = """select a.user_id, sum(TIMESTAMPDIFF(\
|
|
SECOND, IF(a.launched_at<='%(from_date)s', '%(from_date)s', IFNULL(\
|
|
a.launched_at, '%(from_date)s')), IF(a.terminated_at>='%(to_date)s', \
|
|
'%(to_date)s', IFNULL(a.terminated_at, '%(to_date)s')))*a.memory_mb) as \
|
|
memory_usage, sum(TIMESTAMPDIFF(SECOND, IF(a.launched_at<='%(from_date)s', \
|
|
'%(from_date)s', IFNULL(a.launched_at, '%(from_date)s')), IF(a.terminated_at\
|
|
>='%(to_date)s', '%(to_date)s', IFNULL(a.terminated_at, '%(to_date)s')))\
|
|
*a.vcpus) as vcpus_usage from nova.instances as a LEFT OUTER JOIN \
|
|
nova.instance_metadata as b ON a.uuid=b.instance_uuid where a.project_id\
|
|
='%(prj_id)s' and b.value='shared' and a.launched_at is not NULL and \
|
|
a.launched_at<='%(to_date)s' and (a.terminated_at>='%(from_date)s' or \
|
|
a.terminated_at is NULL) group by user_id
|
|
""" % {"prj_id": prj_id, "from_date": from_date, "to_date": to_date}
|
|
|
|
LOG.debug("persistent servers query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
|
|
# for row in result.fetchall():
|
|
for row in result:
|
|
usage[row[0]] = {"memory": float(row[1]),
|
|
"vcpus": float(row[2])}
|
|
|
|
except SQLAlchemyError as ex:
|
|
raise SynergyError(ex.message)
|
|
finally:
|
|
connection.close()
|
|
|
|
return usage
|
|
|
|
def getProjectServers(self, prj_id):
|
|
connection = self.db_engine.connect()
|
|
servers = []
|
|
|
|
try:
|
|
# retrieve the amount of resources in terms of cores and memory
|
|
QUERY = """select a.uuid, a.vcpus, a.memory_mb, a.root_gb, \
|
|
a.vm_state from nova.instances as a WHERE a.project_id='%(project_id)s' \
|
|
and a.vm_state in ('active', 'building', 'error') and a.deleted_at is NULL \
|
|
and a.terminated_at is NULL""" % {"project_id": prj_id}
|
|
|
|
LOG.debug("getProjectServers query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
|
|
for row in result.fetchall():
|
|
flavor = Flavor()
|
|
flavor.setVCPUs(row[1])
|
|
flavor.setMemory(row[2])
|
|
flavor.setStorage(row[3])
|
|
|
|
server = Server()
|
|
server.setId(row[0])
|
|
server.setState(row[4])
|
|
server.setFlavor(flavor)
|
|
|
|
QUERY = """select `key`, value from nova.instance_metadata \
|
|
where instance_uuid='%(id)s' and deleted_at is NULL""" % {"id": server.getId()}
|
|
|
|
LOG.debug("getProjectServers query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
metadata = {}
|
|
|
|
for row in result.fetchall():
|
|
metadata[row[0]] = row[1]
|
|
|
|
server.setMetadata(metadata)
|
|
|
|
servers.append(server)
|
|
except SQLAlchemyError as ex:
|
|
raise SynergyError(ex.message)
|
|
finally:
|
|
connection.close()
|
|
|
|
return servers
|
|
|
|
def getExpiredServers(self, prj_id, server_ids, TTL):
|
|
servers = []
|
|
connection = self.db_engine.connect()
|
|
|
|
try:
|
|
# retrieve all expired instances for the specified
|
|
# project and expiration time
|
|
ids = ""
|
|
|
|
if server_ids:
|
|
ids = "uuid in ('%s') and " % "', '".join(server_ids)
|
|
|
|
QUERY = """select uuid, vcpus, memory_mb, root_gb, \
|
|
vm_state from nova.instances where project_id = \
|
|
'%(project_id)s' and deleted_at is NULL and (vm_state='error' or \
|
|
(%(server_ids)s vm_state='active' and terminated_at is NULL \
|
|
and timestampdiff(minute, launched_at, utc_timestamp()) >= %(expiration)s))\
|
|
""" % {"project_id": prj_id, "server_ids": ids, "expiration": TTL}
|
|
|
|
LOG.debug("getExpiredServers query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
|
|
for row in result.fetchall():
|
|
flavor = Flavor()
|
|
flavor.setVCPUs(row[1])
|
|
flavor.setMemory(row[2])
|
|
flavor.setStorage(row[3])
|
|
|
|
server = Server()
|
|
server.setId(row[0])
|
|
server.setState(row[4])
|
|
server.setFlavor(flavor)
|
|
|
|
QUERY = """select `key`, value from nova.instance_metadata \
|
|
where instance_uuid='%(id)s' and deleted_at is NULL""" % {"id": server.getId()}
|
|
|
|
LOG.debug("getExpiredServers query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
metadata = {}
|
|
|
|
for row in result.fetchall():
|
|
metadata[row[0]] = row[1]
|
|
|
|
server.setMetadata(metadata)
|
|
|
|
servers.append(server)
|
|
|
|
except SQLAlchemyError as ex:
|
|
raise SynergyError(ex.message)
|
|
finally:
|
|
connection.close()
|
|
|
|
return servers
|
|
|
|
def selectComputes(self, request):
|
|
target = self.messaging.getTarget(topic='scheduler',
|
|
exchange="nova",
|
|
version="4.0")
|
|
|
|
client = self.messaging.getRPCClient(target)
|
|
cctxt = client.prepare(version='4.0')
|
|
|
|
request_spec = {
|
|
'image': request.getImage(),
|
|
'instance_properties': request.getInstance(),
|
|
'instance_type': request.getFilterProperties()['instance_type'],
|
|
'num_instances': 1}
|
|
|
|
hosts = cctxt.call(request.getContext(),
|
|
'select_destinations',
|
|
request_spec=request_spec,
|
|
filter_properties=request.getFilterProperties())
|
|
|
|
computes = []
|
|
|
|
for host in hosts:
|
|
compute = Compute()
|
|
compute.setHost(host['host'])
|
|
compute.setNodeName(host['nodename'])
|
|
compute.setLimits(host['limits'])
|
|
|
|
computes.append(compute)
|
|
|
|
return computes
|
|
|
|
def getBlockDeviceMappingList(self, server_id):
|
|
connection = self.db_engine.connect()
|
|
blockDeviceMapList = []
|
|
|
|
try:
|
|
QUERY = """select id, created_at, updated_at, deleted_at, \
|
|
device_name, delete_on_termination, snapshot_id, volume_id, volume_size, \
|
|
no_device, connection_info, deleted, source_type, destination_type, \
|
|
guest_format, device_type, disk_bus, boot_index, image_id from \
|
|
nova.block_device_mapping where instance_uuid='%(server_id)s'
|
|
""" % {"server_id": server_id}
|
|
|
|
LOG.debug("getBlockDeviceMapping query: %s" % QUERY)
|
|
|
|
result = connection.execute(QUERY)
|
|
|
|
for row in result.fetchall():
|
|
blockDeviceMap = BlockDeviceMapping(row[0])
|
|
blockDeviceMap.setCreatedAt(row[1])
|
|
blockDeviceMap.setUpdatedAt(row[2])
|
|
blockDeviceMap.setDeletedAt(row[3])
|
|
blockDeviceMap.setDeviceName(row[4])
|
|
blockDeviceMap.setDeleteOnTermination(row[5])
|
|
blockDeviceMap.setSnapshotId(row[6])
|
|
blockDeviceMap.setVolumeId(row[7])
|
|
blockDeviceMap.setVolumeSize(row[8])
|
|
blockDeviceMap.setNoDevice(row[9])
|
|
blockDeviceMap.setConnectionInfo(row[10])
|
|
blockDeviceMap.setDeleted(row[11])
|
|
blockDeviceMap.setSourceType(row[12])
|
|
blockDeviceMap.setDestinationType(row[13])
|
|
blockDeviceMap.setGuestFormat(row[14])
|
|
blockDeviceMap.setDeviceType(row[15])
|
|
blockDeviceMap.setDiskBus(row[16])
|
|
blockDeviceMap.setBootIndex(row[17])
|
|
blockDeviceMap.setImageId(row[18])
|
|
blockDeviceMap.setInstanceId(server_id)
|
|
|
|
blockDeviceMapList.append(blockDeviceMap)
|
|
except SQLAlchemyError as ex:
|
|
raise SynergyError(ex.message)
|
|
finally:
|
|
connection.close()
|
|
|
|
return blockDeviceMapList
|