diff --git a/lib/opcodes.py b/lib/opcodes.py
index 1d3179c6a71243460f19e019ec8b96528d7811fa..ff7ec1d3b691968d2de1cad261fc5f137d853431 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -34,6 +34,7 @@ opcodes.
 # pylint: disable-msg=R0903
 
 import logging
+import re
 
 from ganeti import constants
 from ganeti import errors
@@ -77,6 +78,30 @@ _PTagKind = ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES))
 #: List of tag strings
 _PTags = ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
 
+#: OP_ID conversion regular expression
+_OPID_RE = re.compile("([a-z])([A-Z])")
+
+
+def _NameToId(name):
+  """Convert an opcode class name to an OP_ID.
+
+  @type name: string
+  @param name: the class name, as OpXxxYyy
+  @rtype: string
+  @return: the name in the OP_XXXX_YYYY format
+
+  """
+  if not name.startswith("Op"):
+    return None
+  # Note: (?<=[a-z])(?=[A-Z]) would be ideal, since it wouldn't
+  # consume any input, and hence we would just have all the elements
+  # in the list, one by one; but it seems that split doesn't work on
+  # non-consuming input, hence we have to process the input string a
+  # bit
+  name = _OPID_RE.sub(r"\1,\2", name)
+  elems = name.split(",")
+  return "_".join(n.upper() for n in elems)
+
 
 def RequireFileStorage():
   """Checks that file storage is enabled.
@@ -138,7 +163,14 @@ class _AutoOpParamSlots(type):
     """
     assert "__slots__" not in attrs, \
       "Class '%s' defines __slots__ when it should use OP_PARAMS" % name
-    assert "OP_ID" in attrs, "Class '%s' is missing OP_ID attribute" % name
+
+    op_id = _NameToId(name)
+    if "OP_ID" in attrs:
+      assert attrs["OP_ID"] == op_id, ("Class '%s' defining wrong OP_ID"
+                                       " attribute %s, should be %s" %
+                                       (name, attrs["OP_ID"], op_id))
+
+    attrs["OP_ID"] = op_id
 
     # Always set OP_PARAMS to avoid duplicates in BaseOpCode.GetAllParams
     params = attrs.setdefault("OP_PARAMS", [])
@@ -296,7 +328,7 @@ class OpCode(BaseOpCode):
   @ivar priority: Opcode priority for queue
 
   """
-  OP_ID = "OP_ABSTRACT"
+  OP_ID = "OP_CODE"
   WITH_LU = True
   OP_PARAMS = [
     ("dry_run", None, ht.TMaybeBool),
@@ -352,12 +384,14 @@ class OpCode(BaseOpCode):
   def Summary(self):
     """Generates a summary description of this opcode.
 
-    The summary is the value of the OP_ID attribute (without the "OP_" prefix),
-    plus the value of the OP_DSC_FIELD attribute, if one was defined; this field
-    should allow to easily identify the operation (for an instance creation job,
-    e.g., it would be the instance name).
+    The summary is the value of the OP_ID attribute (without the "OP_"
+    prefix), plus the value of the OP_DSC_FIELD attribute, if one was
+    defined; this field should allow to easily identify the operation
+    (for an instance creation job, e.g., it would be the instance
+    name).
 
     """
+    assert self.OP_ID is not None and len(self.OP_ID) > 3
     # all OP_ID start with OP_, we remove that
     txt = self.OP_ID[3:]
     field_name = getattr(self, "OP_DSC_FIELD", None)
diff --git a/test/ganeti.cmdlib_unittest.py b/test/ganeti.cmdlib_unittest.py
index 234178958ba7555354fc55a9287a68aed30d3a00..46e3e8b87c82192b2d35b4c5489cd5996e1654d0 100755
--- a/test/ganeti.cmdlib_unittest.py
+++ b/test/ganeti.cmdlib_unittest.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2008 Google Inc.
+# Copyright (C) 2008, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -85,9 +85,8 @@ class TestIAllocatorChecks(testutils.GanetiTestCase):
         self.cfg = mocks.FakeConfig()
         self.op = opcode
 
-    class TestOpcode(opcodes.OpCode):
-      OP_ID = "OP_TEST"
-      OP_PARAMS = [
+    class OpTest(opcodes.OpCode):
+       OP_PARAMS = [
         ("iallocator", None, ht.NoType),
         ("node", None, ht.NoType),
         ]
@@ -95,7 +94,7 @@ class TestIAllocatorChecks(testutils.GanetiTestCase):
     default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
     other_iallocator = default_iallocator + "_not"
 
-    op = TestOpcode()
+    op = OpTest()
     lu = TestLU(op)
 
     c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
diff --git a/test/ganeti.opcodes_unittest.py b/test/ganeti.opcodes_unittest.py
index bb77ca2115e3e6e6773b81683e302af3ecf75a83..6c2fdb04ba6f7c7150d7eb5c8af75d2f51887f80 100755
--- a/test/ganeti.opcodes_unittest.py
+++ b/test/ganeti.opcodes_unittest.py
@@ -45,6 +45,7 @@ class TestOpcodes(unittest.TestCase):
       self.assert_(cls.OP_ID.startswith("OP_"))
       self.assert_(len(cls.OP_ID) > 3)
       self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
+      self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
 
       self.assertRaises(TypeError, cls, unsupported_parameter="some value")
 
@@ -88,32 +89,30 @@ class TestOpcodes(unittest.TestCase):
       self.assertEqual("OP_%s" % summary, op.OP_ID)
 
   def testSummary(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_DSC_FIELD = "data"
       OP_PARAMS = [
         ("data", ht.NoDefault, ht.TString),
         ]
 
-    self.assertEqual(_TestOp(data="").Summary(), "TEST()")
-    self.assertEqual(_TestOp(data="Hello World").Summary(),
+    self.assertEqual(OpTest(data="").Summary(), "TEST()")
+    self.assertEqual(OpTest(data="Hello World").Summary(),
                      "TEST(Hello World)")
-    self.assertEqual(_TestOp(data="node1.example.com").Summary(),
+    self.assertEqual(OpTest(data="node1.example.com").Summary(),
                      "TEST(node1.example.com)")
 
   def testListSummary(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_DSC_FIELD = "data"
       OP_PARAMS = [
         ("data", ht.NoDefault, ht.TList),
         ]
 
-    self.assertEqual(_TestOp(data=["a", "b", "c"]).Summary(),
+    self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
                      "TEST(a,b,c)")
-    self.assertEqual(_TestOp(data=["a", None, "c"]).Summary(),
+    self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
                      "TEST(a,None,c)")
-    self.assertEqual(_TestOp(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
+    self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
 
   def testOpId(self):
     self.assertFalse(utils.FindDuplicates(cls.OP_ID
@@ -162,8 +161,7 @@ class TestOpcodes(unittest.TestCase):
                            msg="Default value returned by function is callable")
 
   def testValidateNoModification(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_PARAMS = [
         ("nodef", ht.NoDefault, ht.TMaybeString),
         ("wdef", "default", ht.TMaybeString),
@@ -172,7 +170,7 @@ class TestOpcodes(unittest.TestCase):
         ]
 
     # Missing required parameter "nodef"
-    op = _TestOp()
+    op = OpTest()
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertFalse(hasattr(op, "nodef"))
@@ -182,7 +180,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Required parameter "nodef" is provided
-    op = _TestOp(nodef="foo")
+    op = OpTest(nodef="foo")
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
@@ -192,7 +190,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertFalse(hasattr(op, "notype"))
 
     # Missing required parameter "nodef"
-    op = _TestOp(wdef="hello", number=999)
+    op = OpTest(wdef="hello", number=999)
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertFalse(hasattr(op, "nodef"))
@@ -200,7 +198,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Wrong type for "nodef"
-    op = _TestOp(nodef=987)
+    op = OpTest(nodef=987)
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertEqual(op.nodef, 987)
@@ -208,14 +206,14 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Testing different types for "notype"
-    op = _TestOp(nodef="foo", notype=[1, 2, 3])
+    op = OpTest(nodef="foo", notype=[1, 2, 3])
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.nodef, "foo")
     self.assertEqual(op.notype, [1, 2, 3])
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
-    op = _TestOp(nodef="foo", notype="Hello World")
+    op = OpTest(nodef="foo", notype="Hello World")
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.nodef, "foo")
@@ -223,8 +221,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
   def testValidateSetDefaults(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_PARAMS = [
         # Static default value
         ("value1", "default", ht.TMaybeString),
@@ -233,7 +230,7 @@ class TestOpcodes(unittest.TestCase):
         ("value2", lambda: "result", ht.TMaybeString),
         ]
 
-    op = _TestOp()
+    op = OpTest()
     before = op.__getstate__()
     op.Validate(True)
     self.assertNotEqual(op.__getstate__(), before,
@@ -244,7 +241,7 @@ class TestOpcodes(unittest.TestCase):
     self.assert_(op.debug_level is None)
     self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
 
-    op = _TestOp(value1="hello", value2="world", debug_level=123)
+    op = OpTest(value1="hello", value2="world", debug_level=123)
     before = op.__getstate__()
     op.Validate(True)
     self.assertNotEqual(op.__getstate__(), before,
diff --git a/test/ganeti.rapi.baserlib_unittest.py b/test/ganeti.rapi.baserlib_unittest.py
index 570636fa7df8084959548f77946663767fbd8f10..807c34d2735891e0f57ab7b103bc0bd6136b8058 100755
--- a/test/ganeti.rapi.baserlib_unittest.py
+++ b/test/ganeti.rapi.baserlib_unittest.py
@@ -33,53 +33,52 @@ import testutils
 
 
 class TestFillOpcode(unittest.TestCase):
-  class _TestOp(opcodes.OpCode):
-    OP_ID = "RAPI_TEST_OP"
+  class OpTest(opcodes.OpCode):
     OP_PARAMS = [
       ("test", None, ht.TMaybeString),
       ]
 
   def test(self):
     for static in [None, {}]:
-      op = baserlib.FillOpcode(self._TestOp, {}, static)
-      self.assertTrue(isinstance(op, self._TestOp))
+      op = baserlib.FillOpcode(self.OpTest, {}, static)
+      self.assertTrue(isinstance(op, self.OpTest))
       self.assertFalse(hasattr(op, "test"))
 
   def testStatic(self):
-    op = baserlib.FillOpcode(self._TestOp, {}, {"test": "abc"})
-    self.assertTrue(isinstance(op, self._TestOp))
+    op = baserlib.FillOpcode(self.OpTest, {}, {"test": "abc"})
+    self.assertTrue(isinstance(op, self.OpTest))
     self.assertEqual(op.test, "abc")
 
     # Overwrite static parameter
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, {"test": 123}, {"test": "abc"})
+                      self.OpTest, {"test": 123}, {"test": "abc"})
 
   def testType(self):
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, {"test": [1, 2, 3]}, {})
+                      self.OpTest, {"test": [1, 2, 3]}, {})
 
   def testStaticType(self):
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, {}, {"test": [1, 2, 3]})
+                      self.OpTest, {}, {"test": [1, 2, 3]})
 
   def testUnicode(self):
-    op = baserlib.FillOpcode(self._TestOp, {u"test": "abc"}, {})
-    self.assertTrue(isinstance(op, self._TestOp))
+    op = baserlib.FillOpcode(self.OpTest, {u"test": "abc"}, {})
+    self.assertTrue(isinstance(op, self.OpTest))
     self.assertEqual(op.test, "abc")
 
-    op = baserlib.FillOpcode(self._TestOp, {}, {u"test": "abc"})
-    self.assertTrue(isinstance(op, self._TestOp))
+    op = baserlib.FillOpcode(self.OpTest, {}, {u"test": "abc"})
+    self.assertTrue(isinstance(op, self.OpTest))
     self.assertEqual(op.test, "abc")
 
   def testUnknownParameter(self):
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, {"othervalue": 123}, None)
+                      self.OpTest, {"othervalue": 123}, None)
 
   def testInvalidBody(self):
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, "", None)
+                      self.OpTest, "", None)
     self.assertRaises(http.HttpBadRequest, baserlib.FillOpcode,
-                      self._TestOp, range(10), None)
+                      self.OpTest, range(10), None)
 
 
 if __name__ == "__main__":