Repository URL to install this package:
Version:
4.3.2 ▾
|
# Copyright 2014 TrilioData Inc.
# All Rights Reserved.
"""
An extension module for novaclient that allows the `nova` application access to
the contego API extensions.
"""
import os
import re
import json
import time
import requests
try:
import urllib.parse as parse
except ImportError:
import urlparse as parse
from novaclient import utils
from novaclient import base
from novaclient import exceptions
from novaclient.v2 import servers
# Add new client capabilities here. Each key is a capability name and its value
# is the list of API capabilities upon which it depends.
CAPABILITIES = {}
CAPS_HELP = {}
def __pre_parse_args__():
pass
def __post_parse_args__(args):
pass
def _find_server(cs, server):
""" Returns a server by name or ID. """
return utils.find_resource(cs.contego, server)
def inherit_args(inherit_from_fn):
"""Decorator to inherit all of the utils.arg decorated agruments from
another function.
"""
def do_inherit(fn):
if hasattr(inherit_from_fn, "arguments"):
fn.arguments = inherit_from_fn.arguments
return fn
return do_inherit
class ContegoServer(servers.Server):
"""
A server object extended to provide contego capabilities
"""
def vast_instance(self):
# return self.manager.vast_snapshot()
pass
class ContegoServerManager(servers.ServerManager):
resource_class = ContegoServer
def __init__(self, client, *args, **kwargs):
servers.ServerManager.__init__(self, client, *args, **kwargs)
# Make sure this instance is available as contego.
if not (hasattr(client, "contego")):
setattr(client, "contego", self)
# Capabilities must be computed lazily because self.api.client isn't
# available in __init__
def setup_capabilities(self):
api_caps = self.get_info()["capabilities"]
self.capabilities = [
cap
for cap in CAPABILITIES.keys()
if all([api_req in api_caps for api_req in CAPABILITIES[cap]])
]
def satisfies(self, requirements):
if not hasattr(self, "capabilities"):
self.setup_capabilities()
return set(requirements) <= set(self.capabilities)
def get_info(self):
url = "/contegoinfo"
res = self.api.client.get(url)[1]
return res
def get_service_list(self):
url = "/os-services"
res = self.api.client.get(url)
return res
def vast_prepare(self, ctx, server, params):
header, info = self._action("contego_vast_prepare", base.getid(server), params)
return info
def vast_freeze(self, server, params):
header, info = self._action("contego_vast_freeze", base.getid(server), params)
return info
def vast_thaw(self, server, params):
header, info = self._action("contego_vast_thaw", base.getid(server), params)
return info
def vast_instance(self, server, params):
header, info = self._action("contego_vast_instance", base.getid(server), params)
return info
def vast_get_info(self, server, params):
header, info = self._action("contego_vast_get_info", base.getid(server), params)
return info
def vast_data_url(self, server, params):
header, info = self._action("contego_vast_data_url", base.getid(server), params)
return info
def vast_data_transfer(self, server, params, do_checksum=True):
header, info = self._action(
"contego_vast_data_transfer", base.getid(server), params
)
return info
def vast_check_prev_snapshot(self, server, params, do_checksum=True):
header, info = self._action(
"contego_vast_check_prev_snapshot", base.getid(server), params
)
return info
def vast_async_task_status(self, server, params, do_checksum=True):
header, info = self._action(
"contego_vast_async_task_status", base.getid(server), params
)
return info
def vast_finalize(self, server, params):
header, info = self._action("contego_vast_finalize", base.getid(server), params)
return info
def vast_reset(self, server, params):
header, info = self._action("contego_vast_reset", base.getid(server), params)
return info
def map_snapshot_files(self, server, params):
header, info = self._action(
"contego_map_snapshot_files", base.getid(server), params
)
return info
def copy_backup_image_to_volume(self, server, params, do_checksum=True):
header, info = self._action(
"contego_copy_backup_image_to_volume", base.getid(server), params
)
return info
def testbubble_attach_volume(self, server, params):
header, info = self._action(
"contego_testbubble_attach_volume", base.getid(server), params
)
return info
def testbubble_reboot_instance(self, server, params):
header, info = self._action(
"contego_testbubble_reboot_instance", base.getid(server), params
)
return info
def vast_commit_image(self, server, params):
header, info = self._action(
"contego_vast_commit_image", base.getid(server), params
)
return info
def vast_config_backup(self, backup_id, params):
header, info = self._action("contego_vast_config_backup", backup_id, params)
return info
def vast_disk_check(self, server, params):
header, info = self._action("contego_vast_disk_check", base.getid(server), params)
return info
def vast_clean_nbd_devices(self, ctx, server, params):
header, info = self._action("contego_vast_clean_nbd_devices", base.getid(server), params)
return info
def validate_database_creds(self, config_workload_id, params):
header, info = self._action(
"contego_validate_database_creds", config_workload_id, params
)
return info
def validate_trusted_user_and_key(self, config_workload_id, params):
header, info = self._action(
"contego_validate_trusted_user_and_key", config_workload_id, params
)
return info
def get_controller_nodes(self, config_workload_id):
header, info = self._action("contego_get_controller_nodes", config_workload_id)
return info
def _action(self, action, server, info=None, retry=0, **kwargs):
"""
Perform a server "action" -- reboot/rebuild/resize/etc.
"""
body = {action: info}
self.run_hooks("modify_body_for_action", body, **kwargs)
url = "%s/%s" % (action, base.getid(server))
try:
return self.post(url, body=body)
except Exception as ex:
if retry >= 1:
raise ex
return self._action(action, server, info=info, retry=retry + 1, **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, "POST", **kwargs)
def _cs_request(self, url, method, **kwargs):
management_url = self.api.client.management_url
if not management_url:
self.api.client.authenticate()
if url is None:
# To get API version information, it is necessary to GET
# a dmapi endpoint directly without "v2/<tenant-id>".
magic_tuple = parse.urlsplit(management_url)
scheme, netloc, path, query, frag = magic_tuple
path = re.sub(r"v[1-9]/[a-z0-9]+$", "", path)
url = parse.urlunsplit((scheme, netloc, path, None, None))
else:
if self.api.client.service_catalog:
s_type = self.api.client.service_type
s_url = self.api.client.get_service_url(s_type)
url = os.path.join(self.api.client.get_service_url(s_url, url))
else:
# NOTE(melwitt): The service catalog is not available
# when bypass_url is used.
url = os.path.join(management_url, url)
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
kwargs.setdefault("headers", {})[
"X-Auth-Token"
] = self.api.client.auth_token
if self.api.client.projectid:
kwargs["headers"]["X-Auth-Project-Id"] = self.api.client.projectid
resp, body = self._time_request(url, method, **kwargs)
return resp, body
except exceptions.Unauthorized as e:
try:
# first discard auth token, to avoid the possibly expired
# token being re-used in the re-authentication attempt
self.api.client.unauthenticate()
# overwrite bad token
self.keyring_saved = False
self.api.client.authenticate()
kwargs["headers"]["X-Auth-Token"] = self.api.client.auth_token
resp, body = self._time_request(url, method, **kwargs)
return resp, body
except exceptions.Unauthorized:
raise e
def _time_request(self, url, method, **kwargs):
start_time = time.time()
self.times = []
resp, body = self.request(url, method, **kwargs)
self.times.append(("%s %s" % (method, url), start_time, time.time()))
return resp, body
def request(self, url, method, **kwargs):
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.api.client.USER_AGENT
kwargs["headers"]["Accept"] = "application/json"
if "body" in kwargs:
kwargs["headers"]["Content-Type"] = "application/json"
kwargs["data"] = json.dumps(kwargs["body"])
del kwargs["body"]
self.timeout = None
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs["verify"] = self.api.client.verify_cert
self.api.client.http_log_req(method, url, kwargs)
request_func = requests.request
session = self.api.client._get_session(url)
if session:
request_func = session.request
resp = request_func(method, url, **kwargs)
self.api.client.http_log_resp(resp)
if resp.text:
# TODO(dtroyer): verify the note below in a requests context
# NOTE(alaski): Because force_exceptions_to_status_code=True
# httplib2 returns a connection refused event as a 400 response.
# To determine if it is a bad request or refused connection we need
# to check the body. httplib2 tests check for 'Connection refused'
# or actively refused' in the body, so that's what we'll do.
if resp.status_code == 400:
if "Connection refused" in resp.text or "actively refused" in resp.text:
raise exceptions.ConnectionRefused(resp.text)
try:
body = json.loads(resp.text)
except ValueError:
body = None
else:
body = None
if resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method)
return resp, body