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