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