Repository URL to install this package:
|
Version:
4.0.116 ▾
|
# 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
import urllib.parse 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 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_instance_user_data(self, server, params):
header, info = self._action(
"contego_vast_get_instance_user_data",
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 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