|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may: s5 c5 Q. C" R: ]! W
# not use this file except in compliance with the License. You may obtain6 O- ]$ ?, `" @8 C5 J
# a copy of the License at5 d2 F6 |# w: u2 `6 h
#
5 a1 {6 X; a3 u1 e* A$ y8 n" Z# http://www.apache.org/licenses/LICENSE-2.02 t! |4 c& z) a' [ x+ n
#
8 t U. k* G% p# _' X2 a# Unless required by applicable law or agreed to in writing, software. {" s, d7 `; ^9 Q9 E s l
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0 H4 Y# T/ |; ~% T$ i9 R# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1 A! {3 r% F9 M# License for the specific language governing permissions and limitations7 G$ G( f4 r' f/ ]* ~2 s- _3 T
# under the License.' W* W0 H0 |0 q; |% D& E* E* K
from openstack.compute.v2 import metadata
* D9 z5 x- K- t' f" \from openstack import exceptions
1 d( h6 a1 C) l3 J* a$ Tfrom openstack.image.v2 import image
* _/ i, J" _7 l7 s& e* gfrom openstack import resource+ s/ H2 i; S' O) `
from openstack import utils
9 Q: I& t% ^ d2 U, I5 u& i H; d$ w- R& S) U) b7 z& d
CONSOLE_TYPE_ACTION_MAPPING = {
. M7 V5 X* g4 F 'novnc': 'os-getVNCConsole',
& h) ?! [7 }9 e4 m B) c4 Z 'xvpvnc': 'os-getVNCConsole',( f; N* s% W0 W7 _. _
'spice-html5': 'os-getSPICEConsole'," Z0 {- V2 g/ g6 g) D* y) D2 N; l
'rdp-html5': 'os-getRDPConsole',
4 \# v. B1 s3 X' M- i9 H( K 'serial': 'os-getSerialConsole'5 E+ }5 \4 w1 N: ]
}
/ P/ L# R. j! f K: D% @2 Q# P3 M" A, d- v4 S
class Server(resource.Resource, metadata.MetadataMixin, resource.TagMixin):
& B/ j0 U* w. L! l4 }& }3 O( L resource_key = 'server'
1 O7 u6 m) F* f. v* O2 n& z6 c resources_key = 'servers'6 ~: F1 Y8 r3 u5 m
base_path = '/servers'1 F; w8 \4 w4 r: Q/ Z/ _, j
# capabilities
! ~1 W/ d6 u6 J- B' q: _6 q% L7 F9 E allow_create = True
' u# B8 K) X" v0 G7 C allow_fetch = True
; ?6 a# w5 o* d6 s/ V, N$ q allow_commit = True" x1 v+ p. W7 d/ f" t- x( ?
allow_delete = True1 o+ L, ] a. Z) D' \( ^0 x6 f! C
allow_list = True
* ~/ y" A2 u6 P3 j# D. n& m! @ _query_mapping = resource.QueryParameters(
5 z/ a' y5 d) X7 @) a9 h "auto_disk_config", "availability_zone",& s# S$ z9 Y9 [+ N
"created_at", "description", "flavor",$ c9 s7 S+ T* \" y" x
"hostname", "image", "kernel_id", "key_name",/ h( b1 e4 g$ z3 j5 b
"launch_index", "launched_at", "locked_by", "name",. i& y" x2 T! M0 U. o7 y/ l1 Q# j' o
"node", "power_state", "progress", "project_id", "ramdisk_id",# i7 {5 Y6 G) Y9 n
"reservation_id", "root_device_name",
7 d5 s9 \" ~9 C6 G) m: x "status", "task_state", "terminated_at", "user_id",
8 u$ Q3 ]# y# z$ P& U6 W) y; i "vm_state", Z7 d. s1 I) X* J, p3 Y
"sort_key", "sort_dir",3 F; q. x, T/ \- j4 r) W
access_ipv4="access_ip_v4",0 A/ j4 B0 y: ^! [- _
access_ipv6="access_ip_v6",
, C$ R7 M/ c$ r1 u. g has_config_drive="config_drive", [9 V! a. M% |, ]
deleted_only="deleted",
1 W1 w0 s: H" w j3 m. ^ compute_host="host",4 Q1 S1 V) r- Z- a# m- T
is_soft_deleted="soft_deleted",. Z' B& C6 p, d4 s
ipv4_address="ip",! _$ c$ H* l5 g) g1 ~0 ^9 `2 W0 B
ipv6_address="ip6",
5 \' q! N4 {3 W changes_since="changes-since",# m! |) O5 r2 B. c* Q, o
changes_before="changes-before",3 E( S' s% Q1 ?: X
id="uuid",/ A' Y7 c) O8 p2 n# X2 N
all_projects="all_tenants"," L: b9 s+ o' I) J. p) I
**resource.TagMixin._tag_query_parameters
: w! A& K' ?* D; l- [6 D )
1 W& e# z3 Z0 {5 W. n _max_microversion = '2.72'; h( n/ d4 L8 u
#: A list of dictionaries holding links relevant to this server.
# y- X( w' ^- M: }/ {# L links = resource.Body('links')
- B. S/ i' e( v( |* m" F; O access_ipv4 = resource.Body('accessIPv4')
R( L6 ?# ^+ p% M: T access_ipv6 = resource.Body('accessIPv6')) W1 `9 w0 ]5 `
#: A dictionary of addresses this server can be accessed through.: X) `) A! O2 b) l9 u
#: The dictionary contains keys such as ``private`` and ``public``,
0 G3 e" E1 S) r# @; I; O* f! P+ r #: each containing a list of dictionaries for addresses of that type.: q+ Z5 h& b: R3 H6 E5 C
#: The addresses are contained in a dictionary with keys ``addr``
! X) N9 @/ K6 f. e #: and ``version``, which is either 4 or 6 depending on the protocol
2 p) }: ?9 K- K' t #: of the IP address. *Type: dict*
' j" k* c l0 Z+ n, G addresses = resource.Body('addresses', type=dict)
- O* z7 C: b' ^4 t/ x #: When a server is first created, it provides the administrator password.) g4 V' }5 L, `: r
admin_password = resource.Body('adminPass')/ E ~' U% S/ {9 U, X) K2 R
#: A list of an attached volumes. Each item in the list contains at least- d' c* m" _/ ^$ m9 R4 M3 O" f" ?( c
#: an "id" key to identify the specific volumes.
, |( F/ ~0 n& ^. ?+ b attached_volumes = resource.Body(+ ~* y& x' ]4 W8 E0 g; j; o! G
'os-extended-volumes:volumes_attached')) f1 u6 C' {5 Q1 G8 S: d8 E2 S
#: The name of the availability zone this server is a part of.
I9 r4 U+ T" {1 ?9 { availability_zone = resource.Body('OS-EXT-AZ:availability_zone')
( y3 n# q1 Z# `& [. o #: Enables fine grained control of the block device mapping for an* Q5 M4 L s6 N$ J
#: instance. This is typically used for booting servers from volumes.
3 r1 B# {4 Z8 Z1 x5 t: O& P, W/ R block_device_mapping = resource.Body('block_device_mapping_v2'). ^7 d2 r% O/ t- n9 K ], e( S$ a
#: Indicates whether or not a config drive was used for this server.: @9 S1 A6 r7 R" N% J. [
config_drive = resource.Body('config_drive')8 b# R: p/ g2 g* T- c% ] n) p( d6 d
#: The name of the compute host on which this instance is running.3 G8 _4 f v: t, E6 N2 k" M) M7 K
#: Appears in the response for administrative users only.
" h: h1 w1 v% ^. s compute_host = resource.Body('OS-EXT-SRV-ATTR:host'), ?+ b# a% B9 r1 U
#: Timestamp of when the server was created. N6 @' N* w! Z3 l5 m' x* \2 z* h" `
created_at = resource.Body('created')" H& m- X9 G" d! H6 V1 N
#: The description of the server. Before microversion7 D+ g Z0 n- ~( c0 c: x! g$ y L
#: 2.19 this was set to the server name.
i* X- ?# s- [* P. w description = resource.Body('description')
) Z1 ]) D, s0 Q' Z #: The disk configuration. Either AUTO or MANUAL.
h" ^" V# d8 c' _% e4 X disk_config = resource.Body('OS-DCF:diskConfig')
2 { \) U! h6 y; L9 L+ s& \: ?, ^ #: The flavor reference, as a ID or full URL, for the flavor to use for0 R' k3 C* l2 d# r3 u# J
#: this server.
7 t7 j* x5 k- n* Y flavor_id = resource.Body('flavorRef')
9 j) p; X& u. c5 B. F2 G #: The flavor property as returned from server.
8 U/ X! T% ]7 \# g # TODO(gtema): replace with flavor.Flavor addressing flavor.original_name
+ r& Q9 s6 H+ k! \& }1 J: Y& l flavor = resource.Body('flavor', type=dict)
" l- M* e/ i) q3 @ #: Indicates whether a configuration drive enables metadata injection.
6 L" _# M' k7 m: l #: Not all cloud providers enable this feature.. N Q8 x& y# \
has_config_drive = resource.Body('config_drive')
4 |0 m* b) a! N# I$ d #: An ID representing the host of this server.
5 ^, K l, s, k z host_id = resource.Body('hostId')4 A: X5 ]) d8 {' h) S
#: The host status." e+ r6 p D1 \, d1 V+ O3 j( n
host_status = resource.Body('host_status'): E" m1 I' N6 Y; j
#: The hostname set on the instance when it is booted.
: T m% T6 I4 g. A4 M% |$ Y #: By default, it appears in the response for administrative users only.5 {3 X9 v o# \9 q0 O
hostname = resource.Body('OS-EXT-SRV-ATTR:hostname')9 o; i; B1 h. v7 o5 z6 _
#: The hypervisor host name. Appears in the response for administrative3 M9 }0 }3 S7 T: z$ W9 _& l& Y7 b; j
#: users only.
* {0 S- J5 b' s& ~6 v hypervisor_hostname = resource.Body('OS-EXT-SRV-ATTR:hypervisor_hostname')4 P9 j+ n# h$ T! T _' Y K
#: The image reference, as a ID or full URL, for the image to use for
+ g* X: R4 s6 P+ O$ h& z4 E& t& | #: this server.& t! } G2 Z, j7 @: z
image_id = resource.Body('imageRef')" k9 E. S% u, q+ c! m
#: The image property as returned from server.7 T5 l& t) r0 }) |- ]6 E' V" M- |0 v
image = resource.Body('image', type=image.Image). @) {0 N& Y/ E, u5 J8 Z
#: The instance name. The Compute API generates the instance name from the
$ E G5 ~2 l0 p, ^4 h* q* Y #: instance name template. Appears in the response for administrative users
( @7 w5 s1 B: ^ #: only.4 w! e2 p e/ U5 U! V4 q7 S
instance_name = resource.Body('OS-EXT-SRV-ATTR:instance_name'); ]% o7 g: ?# z8 A7 R* C! @
# The locked status of the server6 ]. h* q2 E/ a5 O& `) E; i; r
is_locked = resource.Body('locked', type=bool)+ ~) w' H1 O' F6 Y
#: The UUID of the kernel image when using an AMI. Will be null if not.7 W: Y# }$ K9 z f
#: By default, it appears in the response for administrative users only.7 x+ o% h1 `1 i' ], `8 p3 [" M: |
kernel_id = resource.Body('OS-EXT-SRV-ATTR:kernel_id')
* m6 a$ e6 K* \ #: The name of an associated keypair. a) I! @$ V' j( T
key_name = resource.Body('key_name')
+ m2 ~( s6 g4 h! A& I #: When servers are launched via multiple create, this is the
4 d4 ?3 _ g6 H7 D7 M #: sequence in which the servers were launched. By default, it
( \- e; i; W) L N #: appears in the response for administrative users only.
0 [; o7 k$ [+ E; R$ | launch_index = resource.Body('OS-EXT-SRV-ATTR:launch_index', type=int)
! [) \/ C" Q- u #: The timestamp when the server was launched.2 l% R6 \' X4 D* O) T/ b
launched_at = resource.Body('OS-SRV-USG:launched_at')
% t) J% \; @+ B9 n* j #: The maximum number of servers to create.
' L r0 `$ @% G# e max_count = resource.Body('max_count')8 J" ^& F' |3 F) w4 k# Y
#: Metadata stored for this server. *Type: dict*6 `0 k) S) q4 J u9 i( M5 }1 @) _
metadata = resource.Body('metadata', type=dict)
% o" e* ? _/ a H" L$ [ #: The minimum number of servers to create.
0 Y9 w, L$ Q2 b min_count = resource.Body('min_count')8 m3 J: I& m% D: i' T% B, P
#: A networks object. Required parameter when there are multiple0 B+ F% I; q. c) m; J
#: networks defined for the tenant. When you do not specify the
9 ]4 E" g. G# P+ e R* Z; t# X #: networks parameter, the server attaches to the only network
) k1 O U9 k9 t- _8 n& Z- `8 X2 { #: created for the current tenant.# i" `' l6 M: M
networks = resource.Body('networks')
2 J. g! R5 l9 B: N #: The power state of this server.* A0 P. H6 P m
power_state = resource.Body('OS-EXT-STS:power_state')2 O9 _5 A* A( t1 q1 K7 g
#: While the server is building, this value represents the percentage
/ p6 U. k6 ]6 M2 y! g: _/ j" |/ s7 Q #: of completion. Once it is completed, it will be 100. *Type: int*7 x, ^6 j# e5 r. u6 D3 B+ _5 r! Z
progress = resource.Body('progress', type=int)
7 A+ ]; w' s. b5 i1 K" ^ #: The ID of the project this server is associated with.4 d+ G7 _" x9 Y, ~( I0 o
project_id = resource.Body('tenant_id')4 w* b2 g9 p; y% O5 k: Y* ?$ ]
#: The UUID of the ramdisk image when using an AMI. Will be null if not.
/ [. U* {1 ^2 d& |( ^ #: By default, it appears in the response for administrative users only.( _( e; e- E. j: q
ramdisk_id = resource.Body('OS-EXT-SRV-ATTR:ramdisk_id')# `& M, C9 z# Y ?! |
#: The reservation id for the server. This is an id that can be
; ~( |9 }9 N; f #: useful in tracking groups of servers created with multiple create,5 x, K, M! Y& I$ r6 {. f; e
#: that will all have the same reservation_id. By default, it appears: o/ ] o/ B6 j) o6 g3 x( T; z7 H9 w
#: in the response for administrative users only.; r+ C8 a0 @9 G
reservation_id = resource.Body('OS-EXT-SRV-ATTR:reservation_id')
2 k5 z* j! I/ w/ E, y& v #: The root device name for the instance By default, it appears in the+ y0 L9 D1 @. |, F9 C0 L" h* ?
#: response for administrative users only.) v7 w0 _, \4 I4 t
root_device_name = resource.Body('OS-EXT-SRV-ATTR:root_device_name')7 m- l. a P# U3 t7 U, O- d
#: The dictionary of data to send to the scheduler.3 L5 L2 [& b: l5 M* z
scheduler_hints = resource.Body('OS-SCH-HNT:scheduler_hints', type=dict)
( d Q$ l* N8 V3 i p2 T$ S- ] #: A list of applicable security groups. Each group contains keys for
: H- Q5 i1 n# r9 h #: description, name, id, and rules.5 g: n$ l+ Y0 b- |# u" y" [
security_groups = resource.Body('security_groups',- {% h+ @4 n! w0 ^0 D
type=list, list_type=dict)# B. P+ I. L5 g1 H) N
#: The UUIDs of the server groups to which the server belongs.0 ?7 C$ i E4 M' Y% r! z- x
#: Currently this can contain at most one entry.8 K. L* g: y) y/ N# e: R9 o
server_groups = resource.Body('server_groups', type=list) v# F$ R7 A F6 Z8 v
#: The state this server is in. Valid values include ``ACTIVE``,
. b6 V1 v1 e5 T( X+ z #: ``BUILDING``, ``DELETED``, ``ERROR``, ``HARD_REBOOT``, ``PASSWORD``,
O* Z: Z0 W$ @$ {; `6 v #: ``PAUSED``, ``REBOOT``, ``REBUILD``, ``RESCUED``, ``RESIZED``,( r9 F4 J5 W" `1 J3 U
#: ``REVERT_RESIZE``, ``SHUTOFF``, ``SOFT_DELETED``, ``STOPPED``,
7 ^0 h+ V) ^8 G4 U0 z3 ]6 Z #: ``SUSPENDED``, ``UNKNOWN``, or ``VERIFY_RESIZE``.
! L) ], U$ ~. V2 ?7 X0 H status = resource.Body('status')( ]3 G" P% c. m1 ^& e
#: The task state of this server.
* `( g& ]% A$ d3 q$ `2 f task_state = resource.Body('OS-EXT-STS:task_state')* ^. ~: e( Y( c2 i+ |
#: The timestamp when the server was terminated (if it has been).
2 E1 X: |# {; t" H, S& K' l terminated_at = resource.Body('OS-SRV-USG:terminated_at')
8 K0 u! I4 N" q% N0 @ #: A list of trusted certificate IDs, that were used during image
D+ a0 ~+ P1 w6 V7 ~. h0 K #: signature verification to verify the signing certificate.
: x# t7 |- j- \* X8 n& D \ trusted_image_certificates = resource.Body(4 o3 a9 Q/ ^/ q
'trusted_image_certificates', type=list)# J0 X v! d9 ?/ \- r
#: Timestamp of when this server was last updated.
, |9 _( d$ q3 d3 H" E updated_at = resource.Body('updated')
4 X& @; v! l2 F% B8 w. O, q# g #: Configuration information or scripts to use upon launch.
x* ^2 u! `7 x# g F7 N #: Must be Base64 encoded.( y' D- r# H# g# @" u! w9 d) j
user_data = resource.Body('OS-EXT-SRV-ATTR:user_data')1 X: g" x6 i; a- A+ q* Z# u4 D- K* v
#: The ID of the owners of this server.' F9 z. Q3 Z' t1 Z; ?) d
user_id = resource.Body('user_id')' \8 R' G r; F7 a5 O, y# q
#: The VM state of this server.) j- ^0 {" Z+ p
vm_state = resource.Body('OS-EXT-STS:vm_state')% q% v4 b& }, y4 }
def _prepare_request(self, requires_id=True, prepend_key=True,
$ [! q5 Z( L$ r$ {# a base_path=None, **kwargs):
& u8 w- A. m( l! w request = super(Server, self)._prepare_request(requires_id=requires_id,6 l5 [" ^$ s& r: R; i7 o
prepend_key=prepend_key, I0 Q9 M( {7 J0 q
base_path=base_path)
2 Q5 r' c5 g2 P$ ~$ r2 W server_body = request.body[self.resource_key]! \% ~' U' B5 f! j
# Some names exist without prefix on requests but with a prefix
8 b$ r5 h+ e1 o, T" w4 N # on responses. If we find that we've populated one of these# S. \& v5 x' |* M3 E7 ?- L
# attributes with something and then go to make a request, swap out
' W! x% T' X- r' K # the name to the bare version.
$ v; i ?, P% ^, R: I. x # Availability Zones exist with a prefix on response, but not request, H# [! k6 g, s0 Q( S e
az_key = "OS-EXT-AZ:availability_zone"& R8 t4 z1 x! ^
if az_key in server_body:
9 ]2 f/ v3 O# g* v& h7 q server_body["availability_zone"] = server_body.pop(az_key)! z5 f" T( P) K. i9 r, P4 e
# User Data exists with a prefix on response, but not request
. J1 y6 B$ ?& E4 [0 R. j; _ ud_key = "OS-EXT-SRV-ATTR:user_data"
8 q2 ?; X5 K+ g if ud_key in server_body:; K8 g8 Q/ t1 i9 ]1 d
server_body["user_data"] = server_body.pop(ud_key)
6 Q' Y. x0 W4 J& ^0 z # Scheduler hints are sent in a top-level scope, not within the3 U5 i: z! S9 e5 T3 }+ H7 R( U2 s
# resource_key scope like everything else. If we try to send
! ~/ ]6 e! I p/ M. Z. y; C" T # scheduler_hints, pop them out of the resource_key scope and into
8 O! W' x7 S( t, O, m6 i( q # their own top-level scope.
' V8 A, r( M% |3 ?8 n hint_key = "OS-SCH-HNT:scheduler_hints"
$ K: O* T/ z3 n/ `: M/ b2 f. n2 w if hint_key in server_body:' b/ c# C) {, [
request.body[hint_key] = server_body.pop(hint_key)
) W. U. z: [# X$ |2 c1 Y return request
]' _7 \( Z' Y! ~; Y' F2 s def _action(self, session, body, microversion=None):* N: E' d1 z6 s i/ b6 u4 p k
"""Preform server actions given the message body."""
/ @/ W7 X" Z! K& L6 W% J # NOTE: This is using Server.base_path instead of self.base_path7 O* H+ } c0 Q$ j- f
# as both Server and ServerDetail instances can be acted on, but
; C v; N& }8 N5 ~8 b1 S) P8 j # the URL used is sans any additional /detail/ part.
, i5 a |7 w8 E2 S( _ I; g( N) Y" {; s url = utils.urljoin(Server.base_path, self.id, 'action')
2 G6 N) V* m% v C9 w headers = {'Accept': ''}
& E4 O/ v! H/ ]) }: C/ E response = session.post(, ?8 |5 R& j, l. d/ q
url, json=body, headers=headers, microversion=microversion)
( {: N1 f" X6 f: v exceptions.raise_from_response(response)8 c, w+ {. h: k6 z
return response9 L8 p, _* z8 i0 {
def change_password(self, session, new_password):
" _ u G. J' ^6 `& M! c """Change the administrator password to the given password."""
. }0 c3 Y% c j% L1 [ body = {'changePassword': {'adminPass': new_password}}, J8 W5 @0 s/ P$ G/ {
self._action(session, body)5 g5 c( s4 B [9 b
def get_password(self, session):1 `% f' x. Q& D/ ~ v+ ]9 [
"""Get the encrypted administrator password."""3 T0 N# c2 g! }% G3 X( _( [8 A% H
url = utils.urljoin(Server.base_path, self.id, 'os-server-password')3 c: v( t: w+ z. W1 _+ P
response = session.get(url)
% `: ^ Y7 ]) T, T- a. R# o4 x! L: N exceptions.raise_from_response(response)
3 y$ Z( G' t2 ]8 \ M! P data = response.json(). ~. D6 T) x% G5 ?4 s, R( W
return data.get('password')# p2 A, g- T0 `) Z' p
def reboot(self, session, reboot_type):1 [: s) z! r5 h4 s L" Q" E
"""Reboot server where reboot_type might be 'SOFT' or 'HARD'."""
; b% b2 R, s% R' @0 L; A body = {'reboot': {'type': reboot_type}}
. A5 c% o7 M" y& y( z$ k9 p self._action(session, body): ?0 j9 s9 h7 C6 H; v5 p
def force_delete(self, session):
0 K! d, ]2 [6 E9 b0 |' _3 j """Force delete a server."""
3 A( Q. B/ l5 d$ Q0 p body = {'forceDelete': None} v# w$ @6 m& e! t2 E
self._action(session, body)
2 y& u0 ~0 e: u5 W" y: W; o3 l4 b def rebuild(self, session, name=None, admin_password=None,
) [8 C' ~( Z% N6 O5 I1 C: I0 z preserve_ephemeral=False, image=None,
9 f; O; y9 s$ ~9 }2 f3 a+ D access_ipv4=None, access_ipv6=None,# P1 S& S P3 V1 X9 s6 N
metadata=None, user_data=None):; a" j# o0 h, ~* }3 G1 N& q
"""Rebuild the server with the given arguments."""" U6 W$ f; \& b. F% \$ g: ]
action = {
% V7 w p3 n+ |+ P0 o; R 'preserve_ephemeral': preserve_ephemeral
( i) ~0 v# ?! o4 g7 ~0 i8 u3 _! m }8 O4 E0 i0 A! W1 N _
if image is not None:
+ n, X+ A( b4 V) I9 v action['imageRef'] = resource.Resource._get_id(image)& M0 x2 P5 r( n( Q7 k3 I! q
if name is not None:( C6 Z& d! }+ h$ f" o; p# c
action['name'] = name
1 ^* d4 o. A( L- ~3 o if admin_password is not None:
0 W4 v) _! C9 n" n action['adminPass'] = admin_password: P! T0 N: J) T) r' d
if access_ipv4 is not None:4 Q! t% }% [) b2 E3 X [8 y
action['accessIPv4'] = access_ipv4' {$ k/ g% O! l' |
if access_ipv6 is not None:
/ X5 `( [" j# b action['accessIPv6'] = access_ipv6
7 W" C& |$ J0 m% T- x" s3 N. L& d if metadata is not None:
- a" Z# i1 v, H0 Z7 t; x$ m j$ ] action['metadata'] = metadata/ Y# W2 L8 J( o6 _- ?; k F* r
if user_data is not None:4 n+ \6 u1 |) S8 ~, x' h* H5 u
action['user_data'] = user_data
2 O, l' G$ |: }0 g' C7 ?: Y/ a+ i) S body = {'rebuild': action}4 c2 d7 l: m3 ]$ b) d* `
response = self._action(session, body)
3 ?( g: X: c7 b, v( B. k self._translate_response(response)
2 ?/ L7 i: @, D1 V1 L' S return self
+ l7 b* T4 K- \5 X def resize(self, session, flavor):4 j5 l" l# O# r2 B3 m
"""Resize server to flavor reference."""+ a% ~1 u9 S p0 `2 W
body = {'resize': {'flavorRef': flavor}}
& E0 H2 Z2 D& s2 y, ^7 j self._action(session, body)
9 a3 D1 y( T* [2 n( ]- R def confirm_resize(self, session):" A3 U2 a- l6 W9 _* B' p% ^
"""Confirm the resize of the server."""7 q( G8 j. q! G
body = {'confirmResize': None} I" O+ y& _+ O: m+ S/ C3 G/ H! r
self._action(session, body)5 ]7 [- @5 O2 k7 s1 C$ d8 F
def revert_resize(self, session):
, l- ]" B" M! f( } """Revert the resize of the server."""
, p+ p0 _; j- J1 q0 B body = {'revertResize': None}4 k9 I2 t6 O7 a7 h8 g# D' y
self._action(session, body)
& u) ]3 h: M! ~ def create_image(self, session, name, metadata=None):$ p3 n& b& h d
"""Create image from server."""" V& n+ O: _* e" p1 P4 W
action = {'name': name}1 g" d* V k: M" q, D, Q
if metadata is not None:6 E( @' E3 P( f6 ?+ t% A- K
action['metadata'] = metadata7 t8 h! ~1 t2 {" Q2 ]7 s9 y( f
body = {'createImage': action}
; b' H$ Z& k5 S- ^% T# d # You won't believe it - wait, who am I kidding - of course you will!$ Z/ I) A& k# ^, {% k
# Nova returns the URL of the image created in the Location! s! E; ?; p8 k) x' I) j) w
# header of the response. (what?) But, even better, the URL it responds$ q U( Q) c, O) _0 f" J2 `
# with has a very good chance of being wrong (it is built from9 k! e# C4 c5 d6 Q9 d: q2 T
# nova.conf values that point to internal API servers in any cloud. I9 j3 R# Z" ~# `. F7 M2 ]; g
# large enough to have both public and internal endpoints.
7 ?" m. c P- g# Q E. F7 [, e6 [& p # However, nobody has ever noticed this because novaclient doesn't5 {6 L$ r$ w! w
# actually use that URL - it extracts the id from the end of& l5 z7 C; i' D* U. T" s9 e
# the url, then returns the id. This leads us to question:5 d8 H A. w" L0 j
# a) why Nova is going to return a value in a header- @* b+ u) \2 M7 G
# b) why it's going to return data that probably broken/ W, e/ @1 O1 e: a- s
# c) indeed the very nature of the fabric of reality
: P7 c9 P1 W" U! s0 E # Although it fills us with existential dread, we have no choice but6 J( V1 F4 w# V* A# X( Y" o
# to follow suit like a lemming being forced over a cliff by evil
* J; j* j% R) C # producers from Disney.+ T. J9 ^! @. u% v
microversion = None! |9 N- j8 Y: T& ]5 C
if utils.supports_microversion(session, '2.45'):
" f0 z \8 _& x2 c0 [/ r microversion = '2.45'6 S9 g8 P2 y K! _9 q
response = self._action(session, body, microversion)
, ~& R" t+ c6 U1 H body = None: f0 s0 N. {2 k% C
try:- @, C) g. Y3 _7 m% H0 {5 C& S
# There might be body, might be not
* J1 f9 I b* S5 A$ a body = response.json(), n" L O. R9 `
except Exception:
' h! V; R* ]2 ]( W pass, o% @* S$ C/ _6 h" K$ ?' P
if body and 'image_id' in body:
6 }3 M: t1 O5 W4 t. M3 c# p! W image_id = body['image_id']
$ b n/ B H) H* \( a. ~ else:
" A( ?3 |' z( J image_id = response.headers['Location'].rsplit('/', 1)[1]
9 N' _! O" @1 N8 E5 `+ g9 O return image_id4 b+ Z5 E. |; N: L
def add_security_group(self, session, security_group_name):' N* x6 w- H$ l# `6 ^
body = {"addSecurityGroup": {"name": security_group_name}}0 k5 q' n% ^4 f: @
self._action(session, body)% E, t; Z E9 h* v; p2 w1 e
def remove_security_group(self, session, security_group_name):( A6 q h9 d$ P, m
body = {"removeSecurityGroup": {"name": security_group_name}}% I" q5 F+ I5 z
self._action(session, body)7 w1 v' @* p" u
def reset_state(self, session, state):
4 [+ T) V- i# M T- V# I body = {"os-resetState": {"state": state}}
+ p' L: d* V& f/ ?) n9 J( C self._action(session, body)! G3 i& y' u6 v
def add_fixed_ip(self, session, network_id):
9 A! T4 J" N& d) {9 ? body = {"addFixedIp": {"networkId": network_id}}3 T% d# k0 d, K& I
self._action(session, body)
; Z6 U! W- D p; W! d5 l V) Y6 ~3 } def remove_fixed_ip(self, session, address):
j6 v' m; i& u7 q4 i. f body = {"removeFixedIp": {"address": address}}/ M/ }+ u) Y0 D' ?3 m
self._action(session, body)
# [3 s7 U4 N' p( {& S% o def add_floating_ip(self, session, address, fixed_address=None):# f! f5 H' |! y, [3 j
body = {"addFloatingIp": {"address": address}}
$ j: O/ d& H! r: R if fixed_address is not None:" S+ G! c' b0 |2 ]' O$ Z: m
body['addFloatingIp']['fixed_address'] = fixed_address
9 g5 ?# T' p: A+ c& r* E self._action(session, body), W; E# I! r, [9 E& R( h
def remove_floating_ip(self, session, address):& ]; V8 R! X5 `. {( q2 f y; v3 H
body = {"removeFloatingIp": {"address": address}}4 h2 D/ t1 `' R
self._action(session, body)' i. \( V+ w7 ]8 n( k! V
def backup(self, session, name, backup_type, rotation):
, f g8 H7 M7 u7 F3 a$ r9 V body = {% b9 K5 n' n- O+ j* I5 [, m2 c
"createBackup": {
* Y- m v8 N+ I) O, P E r "name": name,
" c2 P1 I6 q0 V( T "backup_type": backup_type," ]$ q2 U, [7 V) W: g
"rotation": rotation
4 x2 z3 ~% c% L6 M }# \( w/ D5 Q6 n( W4 e
}3 U4 F5 }3 _' A0 v
self._action(session, body)
( Y' ` O; x$ N: v# E% l def pause(self, session):8 X- ?4 b, ~* P. }0 W
body = {"pause": None}/ p1 m9 j6 E8 `6 t; j
self._action(session, body)
. I$ F5 _$ h- O def unpause(self, session):
( j$ z8 C8 ^; n( F. q7 M6 T body = {"unpause": None}
$ {# g0 I! T9 a/ U+ \+ X* b self._action(session, body)
7 a4 r5 J1 e, D5 \( O3 w$ ? def suspend(self, session):! k2 N, L# \" z
body = {"suspend": None}
% Z* M: [& R0 L5 a1 ~% ? self._action(session, body)8 c a* P H6 c3 _
def resume(self, session):' k/ O R4 C+ M
body = {"resume": None}+ D, w+ o+ r% H% F1 M
self._action(session, body)
" o* Q( e1 X* Z$ I7 w' r0 ~0 N$ P def lock(self, session):
" i2 c8 f) ^9 w2 O body = {"lock": None}
# y8 B& x+ X% w$ A$ l self._action(session, body)* F. v! \+ b" A; g, m
def unlock(self, session):
# _2 y# W! A2 E) x body = {"unlock": None}; V$ J) _* g. Q8 Z. s5 ~$ w
self._action(session, body)
! `; o% ^! g8 y def rescue(self, session, admin_pass=None, image_ref=None):* W) g" T' s$ R/ q. H( k! P$ R# T3 v
body = {"rescue": {}} Q& W2 b4 R% k3 z
if admin_pass is not None:
7 h6 T( E3 C. Q3 r body["rescue"]["adminPass"] = admin_pass
5 ^/ e4 x7 h; P0 D2 _# [* x/ n! Y if image_ref is not None:
! j1 v8 v; i4 _: g3 Q/ n# X8 Z body["rescue"]["rescue_image_ref"] = image_ref) U) R, a. _. w+ K' Y
self._action(session, body)
% z$ U! I3 C0 ?7 K8 U def unrescue(self, session):
u8 r+ j6 N( G- V2 j body = {"unrescue": None}3 d" q2 R" ~- `/ \' x7 c+ k2 j$ I/ E8 D
self._action(session, body)3 F; {& I/ q& K8 |+ u$ n) ?3 T6 {1 H- L
def evacuate(self, session, host=None, admin_pass=None, force=None):
- D/ y7 P. f# s4 | body = {"evacuate": {}}7 Y; D- ]1 ~$ {* ?
if host is not None:/ ~' B# f4 w' U, t+ S5 Y8 W( S* f
body["evacuate"]["host"] = host m7 C( h4 F$ W
if admin_pass is not None:
% t5 ] O0 b( Z) ^" X% x& t9 c body["evacuate"]["adminPass"] = admin_pass
% _) K$ F6 \7 ]; N7 a if force is not None:; U4 P# T9 [8 t& D: e0 |3 c. P" q! n: H
body["evacuate"]["force"] = force n( O) @9 S, e ~- l! z
self._action(session, body)
5 J8 Z9 j# @& w! V9 x$ q' m def start(self, session):. U' H. Y( p& E# q4 d# w' n
body = {"os-start": None}# m3 l) j* y' k" t- X
self._action(session, body), {8 x: \6 E8 U
def stop(self, session):7 G2 v" y) X" K4 ~0 W$ R
body = {"os-stop": None}; M! \; F0 Q0 {6 G% `7 P/ B; n; M% ^
self._action(session, body)# V5 P/ q: B2 t
def shelve(self, session):! j* r- ~3 j. K. x
body = {"shelve": None}
+ g; O4 O# q" I8 m% b7 {9 K self._action(session, body)
' i" ?8 M) M1 ]4 ] w+ W" u def unshelve(self, session, availability_zone=None):2 A7 l1 o7 S1 {5 N7 w
body = {"unshelve": None}
, w# F( P% o! c9 z0 L& d" Z if availability_zone:1 P$ i" C$ [9 D0 _5 }
body["unshelve"] = {"availability_zone": availability_zone}7 Q& U% P: z ]% I1 T0 A T' q
self._action(session, body)
3 a" }! f3 o2 Q# @: _( H1 V, b" G def migrate(self, session):
, p N2 g, e' A body = {"migrate": None}( D' B2 P- l' E+ ]9 k/ J
self._action(session, body)
2 s- _8 g1 X: c; S1 n5 B# [7 a def get_console_output(self, session, length=None):* N0 R9 q( m O
body = {"os-getConsoleOutput": {}}6 ]9 A6 w# a6 E1 r
if length is not None:* U' z/ C4 o3 ?$ c7 s& J
body["os-getConsoleOutput"]["length"] = length6 h! s) u; g* w) J; A4 I
resp = self._action(session, body)$ H4 u2 X: b v/ k! R' z- Q- N
return resp.json()
1 R& V3 | l6 u1 a b def live_migrate(self, session, host, force, block_migration,
( Q4 c6 S2 U; @% o* W8 y disk_over_commit=False):" R7 ^0 o( u |
if utils.supports_microversion(session, '2.30'):- k H2 |0 V% B+ Y1 v. b4 Z) F
return self._live_migrate_30(; c3 `* o" T9 A, E
session, host,
0 e' C8 q; B9 [0 e2 j force=force,7 N/ d8 c0 d1 h# b: d" y
block_migration=block_migration)
, h/ z+ F. y2 }7 n- p* O elif utils.supports_microversion(session, '2.25'):
7 O( n' J+ H+ L3 F( r; c" p return self._live_migrate_25(
3 z' h* v6 c9 t8 j session, host,
# {; q6 {. I% `" v0 H force=force,
3 U! Z* n# z6 Q& C0 x3 P3 N7 h) a block_migration=block_migration)
3 c0 F6 ^. l1 {' o x6 T8 b else:
0 q9 D4 q+ a3 r5 n% h return self._live_migrate(- I# e( P0 a) j, e' u _) ^* ]) u
session, host,
$ Z* @/ d h- }' u: X+ J3 c force=force,% T+ Z+ ~2 i% S# v
block_migration=block_migration,' K/ u' l$ l( z0 m) K- {: v
disk_over_commit=disk_over_commit)
1 e) s u d; ?* s! P, [. Q# J. t T def get_console_url(self, session, console_type):
* R9 i( u9 f* e2 } action = CONSOLE_TYPE_ACTION_MAPPING.get(console_type)1 [* b m% v- I9 A
if not action:. K( \# ~+ a" b" m6 v7 Z# B( y C
raise ValueError("Unsupported console type %s" % console_type)4 S% P" X4 P" ^
body = {action: {'type': console_type}}
) c" _; q; i7 {3 F B+ \7 ?: F+ P resp = self._action(session, body)- r# u) W5 E% d4 e0 L) q
return resp.json().get('console')
4 ?( Q! N# e4 R6 P) I def _live_migrate_30(self, session, host, force, block_migration):1 ?. F4 j& V7 C" X
microversion = '2.30'
6 B" r' M# {& o1 Q. k body = {'host': None}# F; i' A9 Y. M+ c/ r* a
if block_migration is None:
0 n/ f% J2 G5 a5 g% q block_migration = 'auto'
9 [* H; C4 \/ b body['block_migration'] = block_migration9 `/ T; p+ a; U$ p7 L6 B8 `0 s& e
if host: j( }: S& S# R+ C: i- b" g5 e
body['host'] = host6 G, @ y* w+ c' t! B- K& d
if force:
" M* n7 |9 \# b- n body['force'] = force3 }0 l0 x8 P1 l8 d" z/ w
self._action(% k: V6 O* q4 ^/ {6 a
session, {'os-migrateLive': body}, microversion=microversion) f- U! n5 ~- G
def _live_migrate_25(self, session, host, force, block_migration):) l! M7 \# R) P5 _; P1 a
microversion = '2.25'
. ]9 I; P. H5 h. ~$ P; Q" M5 _3 F body = {'host': None}
' H e% G: n/ |1 q, S4 P if block_migration is None:
- k0 H2 S( ]0 J0 C block_migration = 'auto'
3 g( Y% S# F" V2 i& w- ` body['block_migration'] = block_migration- r# I2 B; I) B
if host:/ h3 V' J4 x; |) Q8 [3 L8 p
body['host'] = host; N; d& [; Q# r% l! ]# ?" |% t" K
if not force:
5 x. c$ y% I O4 } raise ValueError(. r+ I7 P. L- H, B- p5 g( h
"Live migration on this cloud implies 'force'" J: y _6 y O* l# @( _. G9 K4 Z
" if the 'host' option has been given and it is not"% {9 n2 g4 ]% F n' m" j0 I% k
" possible to disable. It is recommended to not use 'host'"! u% G$ d+ S: ?% T5 t( \& m
" at all on this cloud as it is inherently unsafe, but if"" i) d# q* q3 \
" it is unavoidable, please supply 'force=True' so that it"
3 B z% X7 W0 @( [ " is clear you understand the risks.")
7 B# b7 i& [& B9 l) H7 ] self._action(0 R8 M7 k1 E1 ]
session, {'os-migrateLive': body}, microversion=microversion)5 R- o# `! M* ?5 i! L: K+ | w# @
def _live_migrate(self, session, host, force, block_migration,
9 d1 C3 m1 R$ z7 y4 r! t- d disk_over_commit):
$ L! z, X8 ?& S microversion = None6 t$ t7 Z# C! O+ K! s
body = {& P7 G Q" _" q3 ?/ V9 x( @; t
'host': None,: r0 d/ [/ z7 V* Y3 V. K
}, x& t. _( ` \3 [" v
if block_migration == 'auto':
1 h- l% D2 q8 T% X- F1 i- a0 x# K* N raise ValueError(, j1 |) s4 y: t6 @: m
"Live migration on this cloud does not support 'auto' as"7 @2 M4 t/ T$ A1 q' D2 c
" a parameter to block_migration, but only True and False.")
5 _* h8 m1 @8 C0 r9 l body['block_migration'] = block_migration or False/ S$ i$ x5 p7 ~0 f
body['disk_over_commit'] = disk_over_commit or False$ Q# p6 i& r( n
if host:
! K* W2 m. B! Y/ k body['host'] = host2 U3 t0 C: K" S
if not force:
' B6 J& s9 J G3 A# p( y8 ^7 } raise ValueError(( M% C+ U& j0 U2 J& i+ C
"Live migration on this cloud implies 'force'"
2 N# g) F$ P# G" A, I n* O7 x& T/ T! p8 p& v " if the 'host' option has been given and it is not", H) O, j% y3 s+ h) A6 q6 j* h
" possible to disable. It is recommended to not use 'host'"
* S2 u* w! A& I3 i! N, ~6 ` M " at all on this cloud as it is inherently unsafe, but if"2 s4 a0 a9 l3 @* ]# F. M. H* g
" it is unavoidable, please supply 'force=True' so that it"
2 b( K% W+ A9 q) x, W " is clear you understand the risks.")7 g: |0 }# W% u& S% M5 J
self._action(
0 k5 y6 F+ a, z# j# O session, {'os-migrateLive': body}, microversion=microversion)- a& m( c, `* W6 _; z$ B
def fetch_security_groups(self, session):
' P! o3 J( v6 z, {% n( x """Fetch security groups of a server.
& V5 P( M5 N1 Z4 R9 _& E: @5 O :returns: Updated Server instance.. O* _9 Y, L) W/ x Q: t5 i, U& _
"""
6 n$ m# Q4 p; O5 Z url = utils.urljoin(Server.base_path, self.id, 'os-security-groups')
6 r3 W8 A- j0 s }+ S# ^ response = session.get(url)7 x2 c- m- ^( u
exceptions.raise_from_response(response)( C ?2 b' o1 T) u$ W O% U
try:7 K* `( f0 b# @" s
data = response.json()- |2 g7 V: s& _6 ^' v
if 'security_groups' in data:2 i" Z: j; l' ~ m1 k# a1 p6 P
self.security_groups = data['security_groups']
" j s8 s0 x. |. b. q except ValueError:9 L, v4 Q# G4 B8 n+ L
pass
! m# s* l b5 J7 n return self
& V2 A+ M! w" h: E& o+ A4 ^2 Z7 |& K6 k. I: O3 ~- c
ServerDetail = Server0 f' \8 Q2 b6 y) v. {" m, s' E j
|
|