Revision 24991749

b/lib/cmdlib.py
3228 3228
      logging.error("Failed to create directory '%s'", file_storage_dir)
3229 3229
      return False
3230 3230

  
3231
  # Note: this needs to be kept in sync with adding of disks in
3232
  # LUSetInstanceParams
3231 3233
  for device in instance.disks:
3232 3234
    logging.info("Creating volume %s for instance %s",
3233 3235
                 device.iv_name, instance.name)
......
4667 4669
  """
4668 4670
  HPATH = "instance-modify"
4669 4671
  HTYPE = constants.HTYPE_INSTANCE
4670
  _OP_REQP = ["instance_name", "hvparams"]
4672
  _OP_REQP = ["instance_name"]
4671 4673
  REQ_BGL = False
4672 4674

  
4675
  def CheckArguments(self):
4676
    if not hasattr(self.op, 'nics'):
4677
      self.op.nics = []
4678
    if not hasattr(self.op, 'disks'):
4679
      self.op.disks = []
4680
    if not hasattr(self.op, 'beparams'):
4681
      self.op.beparams = {}
4682
    if not hasattr(self.op, 'hvparams'):
4683
      self.op.hvparams = {}
4684
    self.op.force = getattr(self.op, "force", False)
4685
    if not (self.op.nics or self.op.disks or
4686
            self.op.hvparams or self.op.beparams):
4687
      raise errors.OpPrereqError("No changes submitted")
4688

  
4689
    for item in (constants.BE_MEMORY, constants.BE_VCPUS):
4690
      val = self.op.beparams.get(item, None)
4691
      if val is not None:
4692
        try:
4693
          val = int(val)
4694
        except ValueError, err:
4695
          raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
4696
        self.op.beparams[item] = val
4697
    # Disk validation
4698
    disk_addremove = 0
4699
    for disk_op, disk_dict in self.op.disks:
4700
      if disk_op == constants.DDM_REMOVE:
4701
        disk_addremove += 1
4702
        continue
4703
      elif disk_op == constants.DDM_ADD:
4704
        disk_addremove += 1
4705
      else:
4706
        if not isinstance(disk_op, int):
4707
          raise errors.OpPrereqError("Invalid disk index")
4708
      if disk_op == constants.DDM_ADD:
4709
        mode = disk_dict.setdefault('mode', constants.DISK_RDWR)
4710
        if mode not in (constants.DISK_RDONLY, constants.DISK_RDWR):
4711
          raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode)
4712
        size = disk_dict.get('size', None)
4713
        if size is None:
4714
          raise errors.OpPrereqError("Required disk parameter size missing")
4715
        try:
4716
          size = int(size)
4717
        except ValueError, err:
4718
          raise errors.OpPrereqError("Invalid disk size parameter: %s" %
4719
                                     str(err))
4720
        disk_dict['size'] = size
4721
      else:
4722
        # modification of disk
4723
        if 'size' in disk_dict:
4724
          raise errors.OpPrereqError("Disk size change not possible, use"
4725
                                     " grow-disk")
4726

  
4727
    if disk_addremove > 1:
4728
      raise errors.OpPrereqError("Only one disk add or remove operation"
4729
                                 " supported at a time")
4730

  
4731
    # NIC validation
4732
    nic_addremove = 0
4733
    for nic_op, nic_dict in self.op.nics:
4734
      if nic_op == constants.DDM_REMOVE:
4735
        nic_addremove += 1
4736
        continue
4737
      elif nic_op == constants.DDM_ADD:
4738
        nic_addremove += 1
4739
      else:
4740
        if not isinstance(nic_op, int):
4741
          raise errors.OpPrereqError("Invalid nic index")
4742

  
4743
      # nic_dict should be a dict
4744
      nic_ip = nic_dict.get('ip', None)
4745
      if nic_ip is not None:
4746
        if nic_ip.lower() == "none":
4747
          nic_dict['ip'] = None
4748
        else:
4749
          if not utils.IsValidIP(nic_ip):
4750
            raise errors.OpPrereqError("Invalid IP address '%s'" % nic_ip)
4751
      # we can only check None bridges and assign the default one
4752
      nic_bridge = nic_dict.get('bridge', None)
4753
      if nic_bridge is None:
4754
        nic_dict['bridge'] = self.cfg.GetDefBridge()
4755
      # but we can validate MACs
4756
      nic_mac = nic_dict.get('mac', None)
4757
      if nic_mac is not None:
4758
        if self.cfg.IsMacInUse(nic_mac):
4759
          raise errors.OpPrereqError("MAC address %s already in use"
4760
                                     " in cluster" % nic_mac)
4761
        if nic_mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
4762
          if not utils.IsValidMac(nic_mac):
4763
            raise errors.OpPrereqError("Invalid MAC address %s" % nic_mac)
4764
    if nic_addremove > 1:
4765
      raise errors.OpPrereqError("Only one NIC add or remove operation"
4766
                                 " supported at a time")
4767

  
4673 4768
  def ExpandNames(self):
4674 4769
    self._ExpandAndLockInstance()
4675 4770
    self.needed_locks[locking.LEVEL_NODE] = []
4676 4771
    self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4677 4772

  
4678

  
4679 4773
  def DeclareLocks(self, level):
4680 4774
    if level == locking.LEVEL_NODE:
4681 4775
      self._LockInstancesNodes()
......
4691 4785
      args['memory'] = self.be_new[constants.BE_MEMORY]
4692 4786
    if constants.BE_VCPUS in self.be_new:
4693 4787
      args['vcpus'] = self.be_new[constants.BE_VCPUS]
4694
    if self.do_ip or self.do_bridge or self.mac:
4695
      if self.do_ip:
4696
        ip = self.ip
4697
      else:
4698
        ip = self.instance.nics[0].ip
4699
      if self.bridge:
4700
        bridge = self.bridge
4701
      else:
4702
        bridge = self.instance.nics[0].bridge
4703
      if self.mac:
4704
        mac = self.mac
4705
      else:
4706
        mac = self.instance.nics[0].mac
4707
      args['nics'] = [(ip, bridge, mac)]
4788
    # FIXME: readd disk/nic changes
4708 4789
    env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
4709 4790
    nl = [self.cfg.GetMasterNode(),
4710 4791
          self.instance.primary_node] + list(self.instance.secondary_nodes)
......
4716 4797
    This only checks the instance list against the existing names.
4717 4798

  
4718 4799
    """
4719
    # FIXME: all the parameters could be checked before, in ExpandNames, or in
4720
    # a separate CheckArguments function, if we implement one, so the operation
4721
    # can be aborted without waiting for any lock, should it have an error...
4722
    self.ip = getattr(self.op, "ip", None)
4723
    self.mac = getattr(self.op, "mac", None)
4724
    self.bridge = getattr(self.op, "bridge", None)
4725
    self.kernel_path = getattr(self.op, "kernel_path", None)
4726
    self.initrd_path = getattr(self.op, "initrd_path", None)
4727
    self.force = getattr(self.op, "force", None)
4728
    all_parms = [self.ip, self.bridge, self.mac]
4729
    if (all_parms.count(None) == len(all_parms) and
4730
        not self.op.hvparams and
4731
        not self.op.beparams):
4732
      raise errors.OpPrereqError("No changes submitted")
4733
    for item in (constants.BE_MEMORY, constants.BE_VCPUS):
4734
      val = self.op.beparams.get(item, None)
4735
      if val is not None:
4736
        try:
4737
          val = int(val)
4738
        except ValueError, err:
4739
          raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
4740
        self.op.beparams[item] = val
4741
    if self.ip is not None:
4742
      self.do_ip = True
4743
      if self.ip.lower() == "none":
4744
        self.ip = None
4745
      else:
4746
        if not utils.IsValidIP(self.ip):
4747
          raise errors.OpPrereqError("Invalid IP address '%s'." % self.ip)
4748
    else:
4749
      self.do_ip = False
4750
    self.do_bridge = (self.bridge is not None)
4751
    if self.mac is not None:
4752
      if self.cfg.IsMacInUse(self.mac):
4753
        raise errors.OpPrereqError('MAC address %s already in use in cluster' %
4754
                                   self.mac)
4755
      if not utils.IsValidMac(self.mac):
4756
        raise errors.OpPrereqError('Invalid MAC address %s' % self.mac)
4800
    force = self.force = self.op.force
4757 4801

  
4758 4802
    # checking the new params on the primary/secondary nodes
4759 4803

  
......
4844 4888
            self.warn.append("Not enough memory to failover instance to"
4845 4889
                             " secondary node %s" % node)
4846 4890

  
4891
    # NIC processing
4892
    for nic_op, nic_dict in self.op.nics:
4893
      if nic_op == constants.DDM_REMOVE:
4894
        if not instance.nics:
4895
          raise errors.OpPrereqError("Instance has no NICs, cannot remove")
4896
        continue
4897
      if nic_op != constants.DDM_ADD:
4898
        # an existing nic
4899
        if nic_op < 0 or nic_op >= len(instance.nics):
4900
          raise errors.OpPrereqError("Invalid NIC index %s, valid values"
4901
                                     " are 0 to %d" %
4902
                                     (nic_op, len(instance.nics)))
4903
      nic_bridge = nic_dict.get('bridge', None)
4904
      if nic_bridge is not None:
4905
        if not self.rpc.call_bridges_exist(pnode, [nic_bridge]):
4906
          msg = ("Bridge '%s' doesn't exist on one of"
4907
                 " the instance nodes" % nic_bridge)
4908
          if self.force:
4909
            self.warn.append(msg)
4910
          else:
4911
            raise errors.OpPrereqError(msg)
4912

  
4913
    # DISK processing
4914
    if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
4915
      raise errors.OpPrereqError("Disk operations not supported for"
4916
                                 " diskless instances")
4917
    for disk_op, disk_dict in self.op.disks:
4918
      if disk_op == constants.DDM_REMOVE:
4919
        if len(instance.disks) == 1:
4920
          raise errors.OpPrereqError("Cannot remove the last disk of"
4921
                                     " an instance")
4922
        ins_l = self.rpc.call_instance_list([pnode], [instance.hypervisor])
4923
        ins_l = ins_l[pnode]
4924
        if not type(ins_l) is list:
4925
          raise errors.OpPrereqError("Can't contact node '%s'" % pnode)
4926
        if instance.name in ins_l:
4927
          raise errors.OpPrereqError("Instance is running, can't remove"
4928
                                     " disks.")
4929

  
4930
      if (disk_op == constants.DDM_ADD and
4931
          len(instance.nics) >= constants.MAX_DISKS):
4932
        raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
4933
                                   " add more" % constants.MAX_DISKS)
4934
      if disk_op not in (constants.DDM_ADD, constants.DDM_REMOVE):
4935
        # an existing disk
4936
        if disk_op < 0 or disk_op >= len(instance.disks):
4937
          raise errors.OpPrereqError("Invalid disk index %s, valid values"
4938
                                     " are 0 to %d" %
4939
                                     (disk_op, len(instance.disks)))
4940

  
4847 4941
    return
4848 4942

  
4849 4943
  def Exec(self, feedback_fn):
4850 4944
    """Modifies an instance.
4851 4945

  
4852 4946
    All parameters take effect only at the next restart of the instance.
4947

  
4853 4948
    """
4854 4949
    # Process here the warnings from CheckPrereq, as we don't have a
4855 4950
    # feedback_fn there.
......
4858 4953

  
4859 4954
    result = []
4860 4955
    instance = self.instance
4861
    if self.do_ip:
4862
      instance.nics[0].ip = self.ip
4863
      result.append(("ip", self.ip))
4864
    if self.bridge:
4865
      instance.nics[0].bridge = self.bridge
4866
      result.append(("bridge", self.bridge))
4867
    if self.mac:
4868
      instance.nics[0].mac = self.mac
4869
      result.append(("mac", self.mac))
4956
    # disk changes
4957
    for disk_op, disk_dict in self.op.disks:
4958
      if disk_op == constants.DDM_REMOVE:
4959
        # remove the last disk
4960
        device = instance.disks.pop()
4961
        device_idx = len(instance.disks)
4962
        for node, disk in device.ComputeNodeTree(instance.primary_node):
4963
          self.cfg.SetDiskID(disk, node)
4964
          if not self.rpc.call_blockdev_remove(node, disk):
4965
            self.proc.LogWarning("Could not remove disk/%d on node %s,"
4966
                                 " continuing anyway", device_idx, node)
4967
        result.append(("disk/%d" % device_idx, "remove"))
4968
      elif disk_op == constants.DDM_ADD:
4969
        # add a new disk
4970
        if instance.disk_template == constants.DT_FILE:
4971
          file_driver, file_path = instance.disks[0].logical_id
4972
          file_path = os.path.dirname(file_path)
4973
        else:
4974
          file_driver = file_path = None
4975
        disk_idx_base = len(instance.disks)
4976
        new_disk = _GenerateDiskTemplate(self,
4977
                                         instance.disk_template,
4978
                                         instance, instance.primary_node,
4979
                                         instance.secondary_nodes,
4980
                                         [disk_dict],
4981
                                         file_path,
4982
                                         file_driver,
4983
                                         disk_idx_base)[0]
4984
        new_disk.mode = disk_dict['mode']
4985
        instance.disks.append(new_disk)
4986
        info = _GetInstanceInfoText(instance)
4987

  
4988
        logging.info("Creating volume %s for instance %s",
4989
                     new_disk.iv_name, instance.name)
4990
        # Note: this needs to be kept in sync with _CreateDisks
4991
        #HARDCODE
4992
        for secondary_node in instance.secondary_nodes:
4993
          if not _CreateBlockDevOnSecondary(self, secondary_node, instance,
4994
                                            new_disk, False, info):
4995
            self.LogWarning("Failed to create volume %s (%s) on"
4996
                            " secondary node %s!",
4997
                            new_disk.iv_name, new_disk, secondary_node)
4998
        #HARDCODE
4999
        if not _CreateBlockDevOnPrimary(self, instance.primary_node,
5000
                                        instance, new_disk, info):
5001
          self.LogWarning("Failed to create volume %s on primary!",
5002
                          new_disk.iv_name)
5003
        result.append(("disk/%d" % disk_idx_base, "add:size=%s,mode=%s" %
5004
                       (new_disk.size, new_disk.mode)))
5005
      else:
5006
        # change a given disk
5007
        instance.disks[disk_op].mode = disk_dict['mode']
5008
        result.append(("disk.mode/%d" % disk_op, disk_dict['mode']))
5009
    # NIC changes
5010
    for nic_op, nic_dict in self.op.nics:
5011
      if nic_op == constants.DDM_REMOVE:
5012
        # remove the last nic
5013
        del instance.nics[-1]
5014
        result.append(("nic.%d" % len(instance.nics), "remove"))
5015
      elif nic_op == constants.DDM_ADD:
5016
        # add a new nic
5017
        if 'mac' not in nic_dict:
5018
          mac = constants.VALUE_GENERATE
5019
        else:
5020
          mac = nic_dict['mac']
5021
        if mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
5022
          mac = self.cfg.GenerateMAC()
5023
        new_nic = objects.NIC(mac=mac, ip=nic_dict.get('ip', None),
5024
                              bridge=nic_dict.get('bridge', None))
5025
        instance.nics.append(new_nic)
5026
        result.append(("nic.%d" % (len(instance.nics) - 1),
5027
                       "add:mac=%s,ip=%s,bridge=%s" %
5028
                       (new_nic.mac, new_nic.ip, new_nic.bridge)))
5029
      else:
5030
        # change a given nic
5031
        for key in 'mac', 'ip', 'bridge':
5032
          if key in nic_dict:
5033
            setattr(instance.nics[nic_op], key, nic_dict[key])
5034
            result.append(("nic.%s/%d" % (key, nic_op), nic_dict[key]))
5035

  
5036
    # hvparams changes
4870 5037
    if self.op.hvparams:
4871 5038
      instance.hvparams = self.hv_new
4872 5039
      for key, val in self.op.hvparams.iteritems():
4873 5040
        result.append(("hv/%s" % key, val))
5041

  
5042
    # beparams changes
4874 5043
    if self.op.beparams:
4875 5044
      instance.beparams = self.be_inst
4876 5045
      for key, val in self.op.beparams.iteritems():
b/lib/constants.py
191 191
LDS_DRBD = frozenset([LD_DRBD8])
192 192

  
193 193
# disk access mode
194
DISK_RDONLY = "r"
195
DISK_RDWR = "w"
194
DISK_RDONLY = "ro"
195
DISK_RDWR = "rw"
196 196
DISK_ACCESS_SET = frozenset([DISK_RDONLY, DISK_RDWR])
197 197

  
198 198
# disk replacement mode
......
217 217
INISECT_EXP = "export"
218 218
INISECT_INS = "instance"
219 219

  
220
# dynamic device modification
221

  
222
DDM_ADD = 'add'
223
DDM_REMOVE = 'remove'
224

  
220 225
# common exit codes
221 226
EXIT_SUCCESS = 0
222 227
EXIT_FAILURE = 1
......
376 381
RAPI_ENABLE = True
377 382
RAPI_PORT = 5080
378 383

  
384
# max dynamnic devices
385
MAX_NICS = 8
386
MAX_DISKS = 16
387

  
379 388
# cluster wide default parameters
380 389
DEFAULT_ENABLED_HYPERVISOR = HT_XEN_PVM
381 390

  
b/lib/opcodes.py
424 424
  OP_ID = "OP_INSTANCE_SET_PARAMS"
425 425
  OP_DSC_FIELD = "instance_name"
426 426
  __slots__ = [
427
    "instance_name", "ip", "bridge", "mac",
427
    "instance_name",
428 428
    "hvparams", "beparams", "force",
429
    "nics", "disks",
429 430
    ]
430 431

  
431 432

  
b/scripts/gnt-instance
1035 1035
  @return: the desired exit code
1036 1036

  
1037 1037
  """
1038
  if not (opts.ip or opts.bridge or opts.mac or
1038
  if not (opts.nics or opts.disks or
1039 1039
          opts.hypervisor or opts.beparams):
1040 1040
    ToStderr("Please give at least one of the parameters.")
1041 1041
    return 1
......
1044 1044
    opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
1045 1045
      opts.beparams[constants.BE_MEMORY])
1046 1046

  
1047
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1048
    try:
1049
      nic_op = int(nic_op)
1050
      opts.nics[idx] = (nic_op, nic_dict)
1051
    except ValueError:
1052
      pass
1053

  
1054
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1055
    try:
1056
      disk_op = int(disk_op)
1057
      opts.disks[idx] = (disk_op, disk_dict)
1058
    except ValueError:
1059
      pass
1060
    if disk_op == constants.DDM_ADD:
1061
      if 'size' not in disk_dict:
1062
        raise errors.OpPrereqError("Missing required parameter 'size'")
1063
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1064

  
1047 1065
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1048
                                   ip=opts.ip,
1049
                                   bridge=opts.bridge, mac=opts.mac,
1066
                                   nics=opts.nics,
1067
                                   disks=opts.disks,
1050 1068
                                   hvparams=opts.hypervisor,
1051 1069
                                   beparams=opts.beparams,
1052 1070
                                   force=opts.force)
......
1248 1266
                    "Replaces all disks for the instance"),
1249 1267
  'modify': (SetInstanceParams, ARGS_ONE,
1250 1268
             [DEBUG_OPT, FORCE_OPT,
1251
              make_option("-i", "--ip", dest="ip",
1252
                          help="IP address ('none' or numeric IP)",
1253
                          default=None, type="string", metavar="<ADDRESS>"),
1254
              make_option("-b", "--bridge", dest="bridge",
1255
                          help="Bridge to connect this instance to",
1256
                          default=None, type="string", metavar="<bridge>"),
1257
              make_option("--mac", dest="mac",
1258
                          help="MAC address", default=None,
1259
                          type="string", metavar="<MACADDRESS>"),
1260 1269
              keyval_option("-H", "--hypervisor", type="keyval",
1261 1270
                            default={}, dest="hypervisor",
1262 1271
                            help="Change hypervisor parameters"),
1263 1272
              keyval_option("-B", "--backend", type="keyval",
1264 1273
                            default={}, dest="beparams",
1265 1274
                            help="Change backend parameters"),
1275
              ikv_option("--disk", help="Disk changes",
1276
                         default=[], dest="disks",
1277
                         action="append",
1278
                         type="identkeyval"),
1279
              ikv_option("--net", help="NIC changes",
1280
                         default=[], dest="nics",
1281
                         action="append",
1282
                         type="identkeyval"),
1266 1283
              SUBMIT_OPT,
1267 1284
              ],
1268 1285
             "<instance>", "Alters the parameters of an instance"),

Also available in: Unified diff