yakumo.nova.v2.server

Package yakumo :: Package nova :: Package v2 :: Module server |
[hide private]
[frames] | no frames]

# Source Code for Module yakumo.nova.v2.server

      1  # Copyright 2014-2017 by Akira Yoshiyama <akirayoshiyama@gmail.com>.
      2  # All Rights Reserved.
      3  #
      4  #    Licensed under the Apache License, Version 2.0 (the "License"); you may
      5  #    not use this file except in compliance with the License. You may obtain
      6  #    a copy of the License at
      7  #
      8  #         http://www.apache.org/licenses/LICENSE-2.0
      9  #
     10  #    Unless required by applicable law or agreed to in writing, software
     11  #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  #    License for the specific language governing permissions and limitations
     14  #    under the License.
     15
     16  """
     17  Resource class and its manager for servers in Compute API v2
     18  """
     19
     20  import time
     21
     22  from yakumo import base
     23  from yakumo.constant import UNDEF
     24  from yakumo import exception
     25  from yakumo import mapper
     26  from yakumo import utils
     27  from . import volume_attachment
     28  from . import interface_attachment
     29  from yakumo.neutron.v2.network import Resource as Network
     30  from yakumo.neutron.v2.port import Resource as Port
     31  from yakumo.cinder.v2.volume import Resource as Volume
     32  from yakumo.cinder.v2.snapshot import Resource as Snapshot
     33  from yakumo.glance.v2.image import Resource as Image
     34
     35
     36  ATTRIBUTE_MAPPING = [
     37      ('id', 'id', mapper.Noop),
     38      ('name', 'name', mapper.Noop),
     39      ('access_ipv4', 'accessIPv4', mapper.Noop),
     40      ('access_ipv6', 'accessIPv6', mapper.Noop),
     41      ('addresses', 'addresses', mapper.Noop),
     42      ('host', 'OS-EXT-SRV-ATTR:host', mapper.Noop),
     43      ('networks', 'networks', mapper.Noop),
     44      ('disks', 'block_device_mapping_v2', mapper.Noop),
     45      ('user_data', 'user_data', mapper.Base64),
     46      ('progress', 'progress', mapper.Noop),
     47      ('status', 'status', mapper.Noop),
     48      ('task_state', 'OS-EXT-STS:task_state', mapper.Noop),
     49      ('created_at', 'created', mapper.DateTime),
     50      ('updated_at', 'updated', mapper.DateTime),
     51      ('metadata', 'metadata', mapper.Noop),
     52      ('flavor', 'flavorRef', mapper.Resource('nova.flavor')),
     53      ('image', 'imageRef', mapper.Resource('image')),
     54      ('project', 'tenant_id', mapper.Resource('project')),
     55      ('user', 'user_id', mapper.Resource('user')),
     56      ('key_pair', 'key_name', mapper.Resource('nova.key_pair')),
     57      ('error_reason', 'fault', mapper.Noop),
     58      ('availability_zone', 'availability_zone',
     59       mapper.Resource('nova.availability_zone')),
     60      ('availability_zone', 'OS-EXT-AZ:availability_zone',
     61       mapper.Resource('nova.availability_zone')),
     62  ]
     63
     64
     65 -class Resource(base.Resource):
     66      """Resource class for servers in Compute API v2"""
     67
     68      _sub_manager_list = {
     69          'volume': volume_attachment.Manager,
     70          'interface': interface_attachment.Manager,
     71      }
     72
     73 -    def wait_for_finished(self, count=10, interval=10):
     74          """
     75          Wait for task finished
     76
     77          @keyword count: Maximum polling time
     78          @type count: int
     79          @keyword interval: Polling interval in seconds
     80          @type interval: int
     81          @rtype: None
     82          """
     83          for i in range(count):
     84              time.sleep(interval)
     85              try:
     86                  self.reload()
     87              except exception.NotFound:
     88                  return
     89              if not self.task_state:
     90                  return
     91
     92 -    def start(self):
     93          """
     94          Start a server
     95
     96          @rtype: None
     97          """
     98          self._http.post(self._url_resource_path, self._id, 'action',
     99                          data=utils.get_json_body("os-start"))
    100
    101 -    def stop(self):
    102          """
    103          Stop a server
    104
    105          @rtype: None
    106          """
    107          self._http.post(self._url_resource_path, self._id, 'action',
    108                          data=utils.get_json_body("os-stop"))
    109
    110 -    def reboot(self, force=False):
    111          """
    112          Reboot a server
    113
    114          @keyword force: Whether reboot type is hard or soft. force=True means
    115          hard reboot.
    116          @type type: bool
    117          @rtype: None
    118          """
    119          if force:
    120              type = "HARD"
    121          else:
    122              type = "SOFT"
    123          self._http.post(self._url_resource_path, self._id, 'action',
    124                          data=utils.get_json_body("reboot", type=type))
    125
    126 -    def pause(self):
    127          """
    128          Pause a server (save to RAM if server is a VM)
    129
    130          @rtype: None
    131          """
    132          self._http.post(self._url_resource_path, self._id, 'action',
    133                          data=utils.get_json_body("pause"))
    134
    135 -    def unpause(self):
    136          """
    137          Unpause a server
    138
    139          @rtype: None
    140          """
    141          self._http.post(self._url_resource_path, self._id, 'action',
    142                          data=utils.get_json_body("unpause"))
    143
    144 -    def suspend(self):
    145          """
    146          Suspend a server (save to disk if server is a VM)
    147
    148          @rtype: None
    149          """
    150          self._http.post(self._url_resource_path, self._id, 'action',
    151                          data=utils.get_json_body("suspend"))
    152
    153 -    def resume(self):
    154          """
    155          Resume a server
    156
    157          @rtype: None
    158          """
    159          self._http.post(self._url_resource_path, self._id, 'action',
    160                          data=utils.get_json_body("resume"))
    161
    162 -    def reset_network(self):
    163          """
    164          Reset networking of a server
    165
    166          @rtype: None
    167          """
    168          self._http.post(self._url_resource_path, self._id, 'action',
    169                          data=utils.get_json_body("resetNetwork"))
    170
    171 -    def inject_network_info(self):
    172          """
    173          Inject network information to a server
    174
    175          @rtype: None
    176          """
    177          self._http.post(self._url_resource_path, self._id, 'action',
    178                          data=utils.get_json_body("injectNetworkInfo"))
    179
    180 -    def lock(self):
    181          """
    182          Lock a server
    183
    184          @rtype: None
    185          """
    186          self._http.post(self._url_resource_path, self._id, 'action',
    187                          data=utils.get_json_body("lock"))
    188
    189 -    def unlock(self):
    190          """
    191          Unlock a server
    192
    193          @rtype: None
    194          """
    195          self._http.post(self._url_resource_path, self._id, 'action',
    196                          data=utils.get_json_body("unlock"))
    197
    198 -    def force_delete(self):
    199          """
    200          Force to delete a server
    201
    202          @rtype: None
    203          """
    204          self._http.post(self._url_resource_path, self._id, 'action',
    205                          data=utils.get_json_body("forceDelete"))
    206
    207 -    def restore(self):
    208          """
    209          Restore a defered-deleted server if available
    210
    211          @rtype: None
    212          """
    213          self._http.post(self._url_resource_path, self._id, 'action',
    214                          data=utils.get_json_body("restore"))
    215
    216 -    def rescue(self, password=None):
    217          """
    218          Create rescue environment for the server
    219
    220          @keyword password: password of the rescue OS
    221          @type password: str
    222          @rtype: None
    223          """
    224          self._http.post(self._url_resource_path, self._id, 'action',
    225                          data=utils.get_json_body("rescue", dminPass=password))
    226
    227 -    def unrescue(self):
    228          """
    229          Terminate the rescue environment
    230
    231          @rtype: None
    232          """
    233          self._http.post(self._url_resource_path, self._id, 'action',
    234                          data=utils.get_json_body("unrescue"))
    235
    236 -    def shelve(self):
    237          """
    238          Shelve a running server
    239
    240          @rtype: None
    241          """
    242          self._http.post(self._url_resource_path, self._id, 'action',
    243                          data=utils.get_json_body("shelve"))
    244
    245 -    def unshelve(self):
    246          """
    247          Restore a shelved server
    248
    249          @rtype: None
    250          """
    251          self._http.post(self._url_resource_path, self._id, 'action',
    252                          data=utils.get_json_body("unshelve"))
    253
    254 -    def delete_shelve(self):
    255          """
    256          Delete a shelved server
    257
    258          @rtype: None
    259          """
    260          self._http.post(self._url_resource_path, self._id, 'action',
    261                          data=utils.get_json_body("shelveOffload"))
    262
    263 -    def create_image(self, name=None, metadata=None):
    264          """
    265          Create server image
    266
    267          @keyword name: Image name
    268          @type name: str
    269          @keyword metadata: Metadata
    270          @type metadata: dict
    271          @rtype: None
    272          """
    273          self._http.post(self._url_resource_path, self._id, 'action',
    274                          data=utils.get_json_body(
    275                              "createImage",
    276                              name=name,
    277                              metadata=metadata))
    278
    279 -    def backup(self, name=None, backup_type=None, rotation=None):
    280          """
    281          Create server backup
    282
    283          @keyword name: name of the backup data
    284          @type name: str
    285          @keyword backup_type: 'daily' or 'weekly'
    286          @type backup_type: str
    287          @keyword rotation: number of backups to maintain
    288          @type rotation: int
    289          @rtype: None
    290          """
    291          self._http.post(self._url_resource_path, self._id, 'action',
    292                          data=utils.get_json_body(
    293                              "createBackup",
    294                              name=name,
    295                              backup_type=backup_type,
    296                              rotation=rotation))
    297
    298 -    def live_migration(self, host=None, disk_over_commit=False):
    299          """
    300          Move a server to another host without rebooting
    301
    302          @keyword host: Destination host
    303          @type host: str
    304          @keyword disk_over_commit: do disk over commit or not
    305          @type disk_over_commit: bool
    306          @rtype: None
    307          """
    308          self._http.post(self._url_resource_path, self._id, 'action',
    309                          data=utils.get_json_body(
    310                              "os-migrateLive",
    311                              host=host,
    312                              block_migration=False,
    313                              disk_over_commit=disk_over_commit))
    314
    315 -    def block_migration(self, host=None, disk_over_commit=False):
    316          """
    317          Move a server to another host without rebooting, with disk copy
    318
    319          @keyword host: Destination host
    320          @type host: str
    321          @keyword disk_over_commit: do disk over commit or not
    322          @type disk_over_commit: bool
    323          @rtype: None
    324          """
    325          self._http.post(self._url_resource_path, self._id, 'action',
    326                          data=utils.get_json_body(
    327                              "os-migrateLive",
    328                              host=host,
    329                              block_migration=True,
    330                              disk_over_commit=disk_over_commit))
    331
    332 -    def evacuate(self, host=None, password=None, shared=True):
    333          """
    334          Move a server to another host without rebooting, with disk copy
    335
    336          @keyword host: Destination host
    337          @type host: str
    338          @keyword password: new administrator password
    339          @type password: str
    340          @keyword shared: whether the vm is on the shared storage
    341          @type shared: bool
    342          @rtype: None
    343          """
    344          self._http.post(self._url_resource_path, self._id, 'action',
    345                          data=utils.get_json_body(
    346                              "evacuate",
    347                              host=host,
    348                              adminPass=password,
    349                              onSharedStorage=shared))
    350
    351 -    def reset_status(self, status=None):
    352          """
    353          Move a server to another host
    354
    355          @keyword status: new status of the server ('active', 'pause', ...)
    356          @type status: str
    357          @rtype: None
    358          """
    359          self._http.post(self._url_resource_path, self._id, 'action',
    360                          data=utils.get_json_body(
    361                              "os-resetState", state=status))
    362
    363 -    def get_vnc_console(self, type='novnc'):
    364          """
    365          Get VNC console
    366
    367          @keyword type: 'novnc' or 'xvpvnc' (required)
    368          @type type: str
    369          @return: Console information
    370          @rtype: dict
    371          """
    372          ret = self._http.post(self._url_resource_path, self._id, 'action',
    373                                data=utils.get_json_body(
    374                                    "os-getVNCConsole",
    375                                    type=type))
    376          return ret.get('console')
    377
    378 -    def get_console_log(self, lines=50):
    379          """
    380          Get console output
    381
    382          @keyword lines: number of lines
    383          @type lines: int
    384          @return: Console logs
    385          @rtype: dict
    386          """
    387          ret = self._http.post(self._url_resource_path, self._id, 'action',
    388                                data=utils.get_json_body(
    389                                    "os-getConsoleOutput",
    390                                    length=lines))
    391          return ret.get('output')
    392
    393 -    def get_diagnostics(self):
    394          """
    395          Get diagnostics
    396
    397          @return: Diagnostics
    398          @rtype: dict
    399          """
    400          return self._http.get(self._url_resource_path, self._id, 'diagnostics')
    401
    402 -    def resize(self, flavor=None, disk_config='AUTO'):
    403          """
    404          Get console output
    405
    406          @keyword flavor: Flavor (required)
    407          @type flavor: yakumo.nova.v2.flavor.Resource
    408          @keyword disk_config: disk configuration ('AUTO')
    409          @type disk_config: str
    410          @rtype: None
    411          """
    412          self._http.post(self._url_resource_path, self._id, 'action',
    413                          data={"resize": {
    414                              "flavorRef": flavor.id,
    415                              "OS-DCF:diskConfig": disk_config}})
    416
    417 -    def confirm_resize(self):
    418          """
    419          Confirm resizing of a server
    420
    421          @rtype: None
    422          """
    423          self._http.post(self._url_resource_path, self._id, 'action',
    424                          data=utils.get_json_body("confirmResize"))
    425
    426 -    def revert_resize(self):
    427          """
    428          Revert resizing of a server
    429
    430          @rtype: None
    431          """
    432          self._http.post(self._url_resource_path, self._id, 'action',
    433                          data=utils.get_json_body("revertResize"))
    434
    435 -    def rebuild(self, image=None, disk_config='AUTO', password=None,
    436                  ipv4=None, ipv6=None, personality=None):
    437          """
    438          Rebuild a server
    439
    440          @keyword image: Image
    441          @type image: yakumo.image.Resource
    442          @keyword disk_config: disk configuration ('AUTO')
    443          @type disk_config: str
    444          @keyword password: admin password
    445          @type password: str
    446          @keyword ipv4: IPv4 address
    447          @type ipv4: str
    448          @keyword ipv6: IPv6 address
    449          @type ipv6: str
    450          @keyword persoality: personality data
    451          @type persoality: [str]
    452
    453          @rtype: None
    454          """
    455          json_body = utils.get_json_body(
    456              "rebuild",
    457              imageRef=image.id,
    458              adminPass=password,
    459              accessIPv4=ipv4,
    460              accessIPv6=ipv6,
    461              personality=personality)
    462          if disk_config is not None:
    463              json_body['rebuild']['OS-DCF:diskConfig'] = disk_config
    464
    465          self._http.post(self._url_resource_path, self._id, 'action',
    466                          data=json_body)
    467
    468 -    def get_actions(self):
    469          """
    470          Get instance actions
    471
    472          @rtype: dict
    473          """
    474          ret = self._http.get(self._url_resource_path, self._id,
    475                               'os-instance-actions')
    476          return ret.get("instanceActions")
    477
    478 -    def get_password(self):
    479          """
    480          Get instance password
    481
    482          @rtype: dict
    483          """
    484          ret = self._http.get(self._url_resource_path, self._id,
    485                               'os-server-password')
    486          return ret.get("password")
    487
    488 -    def clear_password(self):
    489          """
    490          Clear instance password
    491
    492          @rtype: None
    493          """
    494          self._http.delete(self._url_resource_path, self._id,
    495                            'os-server-password')
    496
    497 -    def get_security_groups(self):
    498          """
    499          Get security group list for a server
    500
    501          @return: Security group list
    502          @rtype: [str]
    503          """
    504          ret = self._http.get(self._url_resource_path, self._id,
    505                               'os-security-groups')
    506          return [self._client.security_group_nova.
    507                  get_empty(x.get('id'))
    508                  for x in ret.get('security_groups', [])]
    509
    510 -    def get_metadata(self):
    511          """
    512          Get instance metadata
    513
    514          @return: Metadata
    515          @rtype: dict
    516          """
    517          ret = self._http.get(self._url_resource_path, self._id, 'metadata')
    518          return ret.get('metadata')
    519
    520 -    def set_metadata(self, **metadata):
    521          """
    522          Update instance metadata
    523
    524          @keyword metadata: key=value style.
    525          @type metadata: dict
    526          @rtype: None
    527          """
    528          self._http.post(self._url_resource_path, self._id, 'metadata',
    529                          data={'metadata': metadata})
    530          self.reload()
    531
    532 -    def unset_metadata(self, *keys):
    533          """
    534          Delete instance metadata
    535
    536          @param key: key of the metadata
    537          @type keys: [str]
    538          @rtype: None
    539          """
    540          for key in keys:
    541              self._http.delete(self._url_resource_path, self._id,
    542                                'metadata', key)
    543          self.reload()
    544
    545
    546 -class Manager(base.Manager):
    547      """Manager class for servers in Compute API v2"""
    548
    549      resource_class = Resource
    550      service_type = 'compute'
    551      _attr_mapping = ATTRIBUTE_MAPPING
    552      _hidden_methods = ["update"]
    553      _json_resource_key = 'server'
    554      _json_resources_key = 'servers'
    555      _url_resource_path = '/servers'
    556      _url_resource_list_path = '/servers/detail'
    557
    558 -    def _json2attr(self, json_params):
    559          flavor_id = json_params.pop('flavor', {}).get('id')
    560          ret = super(Manager, self)._json2attr(json_params)
    561          if flavor_id:
    562              ret['flavor'] = self._client.flavor.get_empty(flavor_id)
    563          return ret
    564
    565 -    def create(self, name=UNDEF, image=UNDEF, flavor=UNDEF,
    566                 personality=UNDEF, disks=UNDEF, max_count=UNDEF,
    567                 min_count=UNDEF, networks=UNDEF, security_groups=UNDEF,
    568                 availability_zone=UNDEF, metadata=UNDEF,
    569                 config_drive=UNDEF, key_pair=UNDEF, user_data=UNDEF):
    570          """Create a new server
    571
    572          @keyword name: name of the new server (required)
    573          @type name: str
    574          @keyword flavor: Flavor object to use (required)
    575          @type flavor: yakumo.nova.v2.flavor.Resource
    576          @keyword image: Image object to use for ephemeral disk
    577          @type image: yakumo.image.Resource
    578          @keyword key_pair: KeyPair object to use
    579          @type key_pair: yakumo.nova.v2.key_pair.Resource
    580          @keyword networks: list of networks or ones with tag and/or fixed IP
    581          @type networks: [yakumo.network.Resource]
    582          @keyword security_groups: list of SecurityGroup object(s) to use
    583          @type security_groups: [yakumo.nova.v2.security_group.Resource]
    584          @keyword disks: block device mapping
    585          @type disks: [dict]
    586          @keyword personality: file path and the content to embed
    587          @type personality: dict
    588          @keyword max_count: the maximum number of server(s) to create
    589          @type max_count: int
    590          @keyword min_count: the minimun number of server(s) to create
    591          @type min_count: int
    592          @keyword availability_zone: Availability Zone
    593          @type availability_zone: yakumo.availability_zone.Resource
    594          @keyword metadata: Metadata
    595          @type metadata: dict
    596          @keyword config_drive: config drive exists or not (bool)
    597          @type config_drive: bool
    598          @keyword user_data: content of a batch file (str)
    599          @type user_data: str
    600          @return: Created server
    601          @rtype: yakumo.nova.v2.server.Resource
    602          """
    603          if networks == UNDEF:
    604              networks = []
    605          if disks == UNDEF:
    606              disks = []
    607          _networks = []
    608          for net in networks:
    609              _network = {}
    610              if isinstance(net, dict):
    611                  if 'tag' in net:
    612                      _network['tag'] = net['tag']
    613                  if 'fixed_ip' in net:
    614                      _network['fixed_ip'] = net['fixed_ip']
    615                  net = net.get('network', net.get('port'))
    616              if isinstance(net, Network):
    617                  _network['uuid'] = net.get_id()
    618              if isinstance(net, Port):
    619                  _network['port'] = net.get_id()
    620              _networks.append(_network)
    621
    622          _disks = []
    623          boot_index = 0
    624          for disk in disks:
    625              _disk = {}
    626              if 'tag' in disk:
    627                  _disk['tag'] = disk['tag']
    628              if 'size' in disk:
    629                  _disk['volume_size'] = disk['size']
    630              if 'source' in disk:
    631                  _disk['uuid'] = disk['source'].get_id()
    632                  if isinstance(disk['source'], Volume):
    633                      _disk['source_type'] = 'volume'
    634                      _disk['destination_type'] = 'volume'
    635                  elif isinstance(disk['source'], Snapshot):
    636                      _disk['source_type'] = 'snapshot'
    637                      _disk['destination_type'] = 'volume'
    638                  elif isinstance(disk['source'], Image):
    639                      _disk['source_type'] = 'image'
    640                      _disk['destination_type'] = \
    641                          disk.get('destination_type', 'volume')
    642              else:
    643                  _disk['source_type'] = 'blank'
    644                  _disk['destination_type'] = \
    645                      disk.get('destination_type', 'volume')
    646              if 'delete_on_termination' in disk:
    647                  _disk['delete_on_termination'] = \
    648                      disk['delete_on_termination']
    649              if 'guest_format' in disk:
    650                  _disk['guest_format'] = disk['guest_format']
    651              _disk['boot_index'] = boot_index
    652              _disks.append(_disk)
    653              boot_index += 1
    654
    655          return super(Manager, self).create(name=name, image=image,
    656                                             flavor=flavor,
    657                                             personality=personality,
    658                                             disks=_disks,
    659                                             max_count=max_count,
    660                                             min_count=min_count,
    661                                             networks=_networks,
    662                                             security_groups=security_groups,
    663                                             availability_zone=availability_zone,
    664                                             config_drive=config_drive,
    665                                             key_pair=key_pair,
    666                                             metadata=metadata,
    667                                             user_data=user_data)
    668